#!/usr/bin/python3 # Usage: ./deploy-to-multiple-worktrees.py REPO_PATH WORKTREE_DEVELOPMENT_PATH WORKTREE_PRODUCTION_PATH import sys import os import subprocess import re WORKTREE_REGEX = r"^worktree (.*)\nHEAD ([a-f0-9]*)\n(?:branch refs\/heads\/(.*)|detached)$" if len(sys.argv) < 4: print("Usage: ./deploy-to-multiple-worktrees.py REPO_PATH WORKTREE_DEVELOPMENT_PATH WORKTREE_PRODUCTION_PATH") exit(1) REPO = os.path.abspath(sys.argv[1]) WORKTREE_DEVELOPMENT = os.path.abspath(sys.argv[2]) WORKTREE_PRODUCTION = os.path.abspath(sys.argv[3]) class Worktree: def __init__(self, path, branch, revision, version): self.path = path self.branch = branch self.revision = revision self.version = version self.newRevision = None self.newVersion = None def getDataForWorktrees(): ret = subprocess.check_output(["git", "worktree", "list", "--porcelain"], cwd=REPO).decode().strip() blocks = ret.split("\n\n") worktrees = [] for block in blocks: matches = re.search(WORKTREE_REGEX, block) if matches: path = matches.group(1) revision = matches.group(2) branch = matches.group(3) version = getVersion(revision) worktrees.append(Worktree(path, branch, revision, version)) return worktrees def findWorktree(path): for worktree in worktrees: if worktree.path == path: return worktree return None def getVersion(branch): return subprocess.check_output(["git", "describe", "--tags", "--always", "--match", "Release_*", branch], cwd=REPO).decode().strip() def getRevisionForRef(ref): return subprocess.check_output(["git", "rev-list", "-1", ref], cwd=REPO).decode().strip() def getLatestReleaseTag(): process = subprocess.Popen(["git", "for-each-ref", "refs/tags/Release*", "--sort=-creatordate", "--format=%(refname:short)"], stdout=subprocess.PIPE, cwd=REPO) for line in process.stdout: tag = line.decode().rstrip() if isTagVerified(tag): return tag print(f"[WARNING] Tag '{tag}' is not verified, skipping.") raise Exception("No verified 'Release*' tag found!") def isTagVerified(tag): process = subprocess.run(["git", "tag", "--verify", tag], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=REPO) return process.returncode == 0 def updateRepoFromRemote(): subprocess.call(["git", "fetch", "origin", "--prune", "--prune-tags"], cwd=REPO) def checkoutWorktree(worktreePath, ref): subprocess.call(["git", "checkout", "-f", ref], cwd=worktreePath) def cleanWorktree(worktreePath): subprocess.call(["git", "clean", "-f", "-d"], cwd=worktreePath) def updateAppInWorktree(worktreePath): subprocess.call([worktreePath + "/scripts/update.sh"], cwd=worktreePath) def updateAppVersionInWorktree(worktreePath): subprocess.call([worktreePath + "/scripts/update-version.sh"], cwd=worktreePath) worktrees = getDataForWorktrees() updateRepoFromRemote() print("Repo is updated from origin") print("----------------------------------------------") print("----------------------------------------------") developmentWorktree = findWorktree(WORKTREE_DEVELOPMENT) developmentWorktree.newRevision = getRevisionForRef(developmentWorktree.branch) developmentWorktree.newVersion = getVersion(developmentWorktree.revision) print("DEVELOPMENT (" + developmentWorktree.path + ") is on branch " + developmentWorktree.branch) print(developmentWorktree.revision + " = " + developmentWorktree.branch + " (" + developmentWorktree.version + ")") print(developmentWorktree.newRevision + " = origin/" + developmentWorktree.branch + " (" + developmentWorktree.newVersion + ")") if developmentWorktree.revision != developmentWorktree.newRevision: print("-> DEVELOPMENT (" + developmentWorktree.path + ") will be UPDATED") print("----------------------------------------------") checkoutWorktree(developmentWorktree.path, developmentWorktree.branch) cleanWorktree(developmentWorktree.path) print(developmentWorktree.path + " is checked out to " + developmentWorktree.branch + " and cleaned") updateAppInWorktree(developmentWorktree.path) updateAppVersionInWorktree(developmentWorktree.path) print("RVR is updated in " + developmentWorktree.path) elif developmentWorktree.version != developmentWorktree.newVersion: print("-> DEVELOPMENT " + developmentWorktree.path + "'s version info will be UPDATED") updateAppVersionInWorktree(developmentWorktree.path) print("RVR version is updated in " + developmentWorktree.path) else: print("-> DEVELOPMENT (" + developmentWorktree.path + ") WON'T be updated") print("----------------------------------------------") print("----------------------------------------------") productionWorktree = findWorktree(WORKTREE_PRODUCTION) productionWorktree.newVersion = getLatestReleaseTag() productionWorktree.newRevision = getRevisionForRef(productionWorktree.newVersion) print("PRODUCTION (" + productionWorktree.path + ")") print(productionWorktree.revision + " = " + productionWorktree.version) print(productionWorktree.newRevision + " = " + productionWorktree.newVersion) if productionWorktree.revision != productionWorktree.newRevision: print("-> PRODUCTION (" + productionWorktree.path + ") will be UPDATED") checkoutWorktree(productionWorktree.path, productionWorktree.newRevision) cleanWorktree(productionWorktree.path) print(productionWorktree.path + " is checked out to " + productionWorktree.newRevision + " and cleaned") updateAppInWorktree(productionWorktree.path) updateAppVersionInWorktree(productionWorktree.path) print("RVR is updated in " + productionWorktree.path) else: print("-> PRODUCTION (" + productionWorktree.path + ") WON'T be updated") print("----------------------------------------------") print("----------------------------------------------")