gitHTTP.py
Python script, ASCII text executable
1import uuid 2from gitme import app, User, db, bcrypt 3import os 4import shutil 5import config 6import flask 7import git 8import subprocess 9from flask_httpauth import HTTPBasicAuth 10 11auth = HTTPBasicAuth() 12 13 14@auth.verify_password 15def verifyPassword(username, password): 16user = User.query.filter_by(username=username).first() 17 18if user and bcrypt.check_password_hash(user.passwordHashed, password): 19flask.session["username"] = user.username 20flask.g.user = username 21return True 22 23return False 24 25 26@app.route("/git/<username>/<repository>/<path:subpath>", methods=["PROPFIND"]) 27def gitList(username, repository, subpath): 28serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository, ".git", subpath) 29 30if not os.path.exists(serverRepoLocation): 31flask.abort(404) 32 33text = """<?xml version="1.0" encoding="utf-8" ?> 34<D:multistatus xmlns:D="DAV:"> 35""" 36 37for file in os.listdir(serverRepoLocation): 38text += f""" 39<D:response> 40<D:href>/git/{username}/{repository}/</D:href> 41""" 42 43if os.path.isdir(file): 44text += """ 45<D:propstat> 46<D:prop> 47<D:resourcetype> 48<D:collection/> 49</D:resourcetype> 50</D:prop> 51<D:status>HTTP/1.1 200 OK</D:status> 52</D:propstat> 53""" 54else: 55text += """ 56<D:propstat> 57<D:prop/> 58<D:status>HTTP/1.1 200 OK</D:status> 59</D:propstat> 60""" 61 62text += """ 63</D:response>""" 64 65text += """ 66</D:multistatus>""" 67 68return text 69 70 71@app.route("/git/<username>/<repository>/", methods=["GET", "POST"]) 72def gitRoot(username, repository): 73serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository)) 74app.logger.info(f"Loading {serverRepoLocation}") 75 76if not os.path.exists(serverRepoLocation): 77app.logger.error(f"Cannot load {serverRepoLocation}") 78flask.abort(404) 79 80repo = git.Repo(serverRepoLocation) 81 82 83@app.route("/git/<username>/<repository>/<path:git_path>", methods=["GET", "POST"]) 84@auth.login_required 85def gitInfo(username, repository, git_path=""): 86serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository)) 87app.logger.info(f"Loading {serverRepoLocation}") 88 89if not os.path.exists(serverRepoLocation): 90app.logger.error(f"Cannot load {serverRepoLocation}") 91flask.abort(404) 92 93repo = git.Repo(serverRepoLocation) 94 95if flask.request.method == "GET": 96method = "upload-pack" 97result = repo.git.upload_pack() 98elif flask.request.method == "POST": 99method = "receive-pack" 100if flask.request.headers.get("Content-Type") == "application/x-git-receive-pack-request": 101result = repo.git.receive_pack() 102else: 103result = "Not supported" 104 105response = flask.Response(result, content_type=f"application/x-git-{method}-result") 106return response 107 108 109@app.route("/git/<username>/<repository>/<path:git_path>", methods=["MKCOL"]) 110def gitDummyMkCol(username, repository, git_path): 111return "", 200 112 113 114@app.route("/git/<username>/<repository>/info/", methods=["MKCOL"]) 115def gitMakeInfo(username, repository): 116serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository)) 117app.logger.info(f"Loading {serverRepoLocation}") 118 119if not os.path.exists(serverRepoLocation): 120app.logger.error(f"Cannot load {serverRepoLocation}") 121flask.abort(404) 122 123if not os.path.exists(os.path.join(serverRepoLocation, ".git", "info")): 124os.makedirs(os.path.join(serverRepoLocation, ".git", "info")) 125 126return "", 200 127 128 129@app.route("/git/<username>/<repository>/info/refs") 130def gitInfoRefs(username, repository): 131serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository)) 132app.logger.info(f"Loading {serverRepoLocation}") 133 134if not os.path.exists(serverRepoLocation): 135app.logger.error(f"Cannot load {serverRepoLocation}") 136flask.abort(404) 137 138repo = git.Repo(serverRepoLocation) 139text = f"{repo.heads[0].commit.hexsha}\tHEAD\n" 140for ref in repo.heads: 141text += f"{ref.commit.hexsha}\trefs/heads/{ref.name}\n" 142 143app.logger.warning(text) 144 145return flask.Response(text, content_type=f"application/x-{flask.request.args.get('service')}-result") 146 147 148@app.route("/git/<username>/<repository>/objects/info/alternates", methods=["GET"]) 149@auth.login_required 150def gitInfoAlternates(username, repository): 151text = "#Alternate object stores\n" 152return flask.Response(text, content_type="text/plain") 153 154 155@app.route("/git/<username>/<repository>/objects/info/http-alternates") 156@auth.login_required 157def gitHttpAlternates(username, repository): 158return flask.Response("", content_type="text/plain") 159 160 161@app.route("/git/<username>/<repository>/HEAD") 162@auth.login_required 163def gitHead(username, repository): 164serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository)) 165app.logger.info(f"Loading {serverRepoLocation}") 166 167if not os.path.exists(serverRepoLocation): 168app.logger.error(f"Cannot load {serverRepoLocation}") 169flask.abort(404) 170 171repo = git.Repo(serverRepoLocation) 172text = f"ref: {repo.head.ref}\n" 173return flask.Response(text, content_type="text/plain") 174 175 176@app.route("/git/<username>/<repository>/objects/info/packs") 177def gitInfoPacks(username, repository): 178serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository, ".git") 179packs = [f for f in os.listdir(os.path.join(serverRepoLocation, "objects", "pack")) if f.endswith(".pack")] 180text = "\n".join(packs) + "\n" 181return flask.Response(text, content_type="text/plain") 182 183 184@app.route("/git/<username>/<repository>/objects/<path:objectPath>") 185@auth.login_required 186def gitObject(username, repository, objectPath): 187serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository)) 188objectFullPath = os.path.join(serverRepoLocation, ".git/objects", objectPath) 189 190if not os.path.exists(objectFullPath): 191return flask.Response("Object not found", status=404, content_type="text/plain") 192 193with open(objectFullPath, "rb") as f: 194objectData = f.read() 195 196return flask.Response(objectData, content_type="application/octet-stream") 197 198 199@app.route("/git/<username>/<repository>/<path:itemPath>", methods=["PUT"]) 200# @auth.login_required 201def gitPut(username, repository, itemPath): 202serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository)) 203itemFullPath = os.path.join(serverRepoLocation, ".git", itemPath) 204 205if not os.path.exists(os.path.dirname(itemFullPath)): 206os.makedirs(os.path.dirname(itemFullPath)) 207 208with open(itemFullPath, "wb") as f: 209f.write(flask.request.data) 210 211return "", 200 212 213 214@app.route("/git/<username>/<repository>/<path:itemPath>", methods=["MOVE"]) 215# @auth.login_required 216def gitMove(username, repository, itemPath): 217serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 218itemFullPath = os.path.join(serverRepoLocation, ".git", itemPath) 219newPath = os.path.join(serverRepoLocation, ".git", flask.request.headers["destination"].split("/", 6)[-1]) 220 221app.logger.info(f"move {itemPath} to {newPath}") 222 223shutil.move(itemFullPath, newPath) 224return "", 200 225 226 227@app.route("/git/<username>/<repository>/git-upload-pack", methods=["POST"]) 228@auth.login_required 229def gitUploadPack(username, repository): 230serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository)) 231cmd = ["git", "--git-dir", serverRepoLocation, "upload-pack"] 232 233gitProcess = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 234 235inputData = flask.request.get_data() 236output = gitProcess.communicate(input=inputData) 237return flask.Response(output[0], content_type="application/x-git-upload-pack-result") 238 239 240@app.route("/git/<username>/<repository>/git-receive-pack", methods=["POST"]) 241@auth.login_required 242def gitReceivePack(username, repository): 243serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 244 245proc = subprocess.Popen(['git', '--work-tree', serverRepoLocation, 'receive-pack'], stdin=subprocess.PIPE, 246stdout=subprocess.PIPE) 247proc.stdin.write(flask.request.get_data()) 248proc.stdin.close() 249response = proc.stdout.read() 250 251return flask.Response(response, content_type='application/x-git-receive-pack-result') 252 253 254@app.route("/git/<username>/<repository>/", methods=["PROPFIND"]) 255def gitPropFind(username, repository): 256serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 257 258if not os.path.exists(serverRepoLocation): 259flask.abort(404) 260 261repo = git.Repo(serverRepoLocation) 262 263branches = "" 264for branch in repo.heads: 265branches += f""" 266<ns0:branch xmlns="DAV:"> 267<ns0:name>{branch.name}</ns0:name> 268</ns0:branch> 269""" 270 271response = f"""<?xml version="1.0" encoding="UTF-8"?> 272<D:multistatus xmlns:D="DAV:"> 273<D:response> 274<D:href>/git/{username}/{repository}/</D:href> 275<D:propstat> 276<D:status>HTTP/1.1 200 OK</D:status> 277<D:prop> 278<D:displayname>{repository}</D:displayname> 279<D:resourcetype><D:collection/></D:resourcetype> 280<D:supportedlock> 281<D:lockentry> 282<D:lockscope><D:exclusive/></D:lockscope> 283<D:locktype><D:write/></D:locktype> 284</D:lockentry> 285</D:supportedlock> 286</D:prop> 287</D:propstat> 288</D:response> 289</D:multistatus> 290""" 291 292return flask.Response(response, content_type="application/xml") 293 294 295locks = {} 296 297 298@app.route("/git/<username>/<repository>/<path:subpath>", methods=["LOCK", "UNLOCK"]) 299def gitLock(username, repository, subpath): 300if flask.request.method == "LOCK": 301scope = flask.request.args.get("lockscope") 302type_ = flask.request.args.get("locktype") 303 304if username + "/" + repository in locks: 305return "Lock already exists", 423 306 307lockToken = uuid.uuid4().hex 308if config.locking: 309locks[username + "/" + repository] = { 310"scope": scope, 311"type": type_, 312"token": lockToken 313} 314 315text = f"""<?xml version="1.0" encoding="UTF-8"?> 316<D:prop> 317<D:lockdiscovery> 318<D:activelock> 319<D:lockscope><D:exclusive/></D:lockscope> 320<D:locktype><D:write/></D:locktype> 321<D:depth>Infinity</D:depth> 322<D:owner> 323<D:href>https://{config.BASE_DOMAIN}/{username}</D:href> 324</D:owner> 325<D:timeout>Second-600</D:timeout> 326<D:locktoken> 327<D:href>opaquelocktoken:{lockToken}</D:href> 328</D:locktoken> 329</D:activelock> 330</D:lockdiscovery> 331</D:prop> 332""" 333 334return text 335elif flask.request.method == "UNLOCK": 336lockToken = flask.request.headers.get("Lock-Token") 337 338if config.locking and locks[username + "/" + repository]["token"] == lockToken: 339del locks[username + "/" + repository] 340 341return "" 342