Skip to content

Commit

Permalink
contest: add trivial brancher
Browse files Browse the repository at this point in the history
Add a very trivial brancher to start pushing fake branches
to test. For now those branches are just a copy of net-next.

Signed-off-by: Jakub Kicinski <[email protected]>
  • Loading branch information
kuba-moo committed Nov 10, 2023
1 parent b3593bc commit a4ff842
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 0 deletions.
21 changes: 21 additions & 0 deletions core/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ def git_pull(self, pull_url):
cmd += pull_url.split()
return self.git(cmd)

def git_push(self, remote, spec):
cmd = ["push", remote, spec]
return self.git(cmd)

def git_status(self, untracked=None, short=False):
cmd = ["status"]
if short:
Expand Down Expand Up @@ -104,6 +108,23 @@ def reset(self, fetch=None):
finally:
core.log_end_sec()

def remotes(self):
"""
Returns a dict of dicts like {"origin": {"fetch": URL1, "push": URL2}}
"""
cmd = ["remote", "-v"]
ret = self.git(cmd)
lines = ret.split('\n')
result = {}
for l in lines:
if not l:
continue
bits = l.split()
info = result.get(bits[0], {})
info[bits[2][1:-1]] = bits[1]
result[bits[0]] = info
return result

def contains(self, commit):
core.log_open_sec("Checking for commit " + commit)
try:
Expand Down
177 changes: 177 additions & 0 deletions trivial-net-brancher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0

import configparser
import datetime
import json
import os
import time

from core import NIPA_DIR
from core import log, log_open_sec, log_end_sec, log_init
from core import Tree

"""
Config:
[trees]
net-next=net-next, net-next, origin, origin/main
[target]
public_url=https://github.com/linux-netdev/testing.git
[email protected]:linux-netdev/testing.git
branch_pfx=net-next-
[output]
branches=branches.json
"""


def hour_timestamp(when=None) -> int:
if when is None:
when = datetime.datetime.now(datetime.UTC)
ts = when.timestamp()
return int(ts / (60 * 60))


def dump_branches(config, state) -> None:
log_open_sec("Update branches manifest")
pub_url = config.get('target', 'public_url')

data = []
for name, val in state["branches"].items():
data.append({"branch": name, "date": val, "url": pub_url + " " + name})

branches = config.get("output", "branches")
with open(branches, 'w') as fp:
json.dump(data, fp)
log_end_sec()


def create_new(config, state, tree, tgt_remote) -> None:
now = datetime.datetime.now(datetime.UTC)
pfx = config.get("target", "branch_pfx")
branch_name = pfx + datetime.datetime.now(datetime.UTC).strftime("%Y-%m-%d--%H-%M")

log_open_sec("Fetching latest net-next")
tree.git_fetch(tree.remote)
tree.git_reset(tree.branch, hard=True)
log_end_sec()

state["branches"][branch_name] = now.isoformat()

log_open_sec("Pushing out")
tree.git_push(tgt_remote, "HEAD:" + branch_name)
log_end_sec()


def reap_old(config, state, tree, tgt_remote) -> None:
now = datetime.datetime.now(datetime.UTC)
pfx = config.get("target", "branch_pfx")

log_open_sec("Clean up old branches")
tree.git_fetch(tgt_remote)

branches = tree.git(['branch', '-a'])
branches = branches.split('\n')
r_tgt_pfx = 'remotes/' + tgt_remote + '/'

found = set()
for br in branches:
br = br.strip()
if not br.startswith(r_tgt_pfx + pfx):
continue
br = br[len(r_tgt_pfx):]
found.add(br)
if br not in state["branches"]:
tree.git_push(tgt_remote, ':' + br)
continue
when = datetime.datetime.fromisoformat(state["branches"][br])
if now - when > datetime.timedelta(days=5):
tree.git_push(tgt_remote, ':' + br)
del state["branches"][br]
continue
state_has = set(state["branches"].keys())
lost = state_has.difference(found)
for br in lost:
log_open_sec("Removing lost branch " + br + " from state")
del state["branches"][br]
log_end_sec()
log_end_sec()


def main_loop(config, state, tree, tgt_remote) -> None:
now = datetime.datetime.now(datetime.UTC)
now_h = hour_timestamp(now)
if now_h - state["last"] < 3:
time.sleep(20)
return

reap_old(config, state, tree, tgt_remote)
create_new(config, state, tree, tgt_remote)

state["last"] = now_h

dump_branches(config, state)


def prep_remote(config, tree) -> str:
tgt_tree = config.get('target', 'push_url')

log_open_sec("Prep remote")
remotes = tree.remotes()
for r in remotes:
if remotes[r]["push"] == tgt_tree:
log("Found remote, it is " + r)
return r

log("Remote not found, adding")

if "brancher" in remotes:
log("Remote 'brancher' already exists with different URL")
raise Exception("Remote exists with different URL")

tree.git(['remote', 'add', 'brancher', tgt_tree])
log_end_sec()


def main() -> None:
config = configparser.ConfigParser()
config.read(['nipa.config', 'pw.config', 'brancher.config'])

log_init(config.get('log', 'type', fallback='stdout'),
config.get('log', 'file', fallback=None))

state = {}
if os.path.exists("brancher.state"):
with open("brancher.state") as fp:
state = json.load(fp)

if "last" not in state:
state["last"] = 0
if "branches" not in state:
state["branches"] = {}

tree_obj = None
tree_dir = config.get('dirs', 'trees', fallback=os.path.join(NIPA_DIR, "../"))
for tree in config['trees']:
opts = [x.strip() for x in config['trees'][tree].split(',')]
prefix = opts[0]
fspath = opts[1]
remote = opts[2]
branch = opts[3]
src = os.path.join(tree_dir, fspath)
# name, pfx, fspath, remote=None, branch=None
tree_obj = Tree(tree, prefix, src, remote=remote, branch=branch)
tree = tree_obj

tgt_remote = prep_remote(config, tree)

try:
while True:
main_loop(config, state, tree, tgt_remote)
finally:
with open('brancher.state', 'w') as f:
json.dump(state, f)


if __name__ == "__main__":
main()

0 comments on commit a4ff842

Please sign in to comment.