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