#! /usr/bin/env python3.3
"""A db.change recorder for Git Fusion data such as
our blobs, trees, and commits copied from Git.
"""
import logging
import p4gf_gitmirror
from p4gf_l10n import NTR
import p4gf_p4dbschema
import p4gf_time_zone
LOG = logging.getLogger("p4gf_fast_push.gitmirror_change")
class GitMirrorChange:
"""A db.change recorder for anything that isn't versioned
files from Git:
* blobs
* trees
* commits (the ObjectType gitmirror backup of them)
* depot branch-info files
* Git Fusion repo config files
Knows to "close" the current change, write its db.change and db.desc
records to the zip archive, then "open" a new one after 1 million revs.
Assumes it is the ONLY source of db.change records for its
zipfile. Maintains its own change counter.
Expected call sequence:
Open:
gmchange = GitMirrorChange(ctx, p4unzip)
Write revisions:
gmchange.ensure_writable(...)
change_num = gmchange.change_num
change_date = gmchange.date_seconds()
Close:
gmchange.close()
"""
# Perforce can handle millions of files added
# in a single changelist. But browsing tools
# tend to crash when asked to display such
# changelists. Keep it under one megarev.
MAX_REVS_PER_CHANGELIST = 10**6
def __init__(self, ctx, p4unzip):
self.ctx = ctx
self.p4unzip = p4unzip
self.change_num = 1
self.date_p4_dt = _now_p4_dt(ctx)
self.rev_ct = 0
def db_change(self):
"""Return a db.change journal record as a single string."""
return p4gf_p4dbschema.db_change(
change_num = self.change_num
, client = self.ctx.p4gf.client
, user = self.ctx.config.p4user
, date = self.date_seconds()
, status = p4gf_p4dbschema.ChangeStatus.COMMITTED
, description = self.description()
, root = NTR("//...")
)
def db_desc(self):
"""Return a db.desc journal record as a single string."""
return p4gf_p4dbschema.db_desc( change_num = self.change_num
, description = self.description() )
def description(self):
"""Return our "Repo 'xxx' copied from Git." description."""
return (p4gf_gitmirror.DESCRIPTION
.format(repo = self.ctx.config.repo_name))
def date_seconds(self):
"""Return seconds since the epoch, in Perforce server timezone."""
return int(self.date_p4_dt.timestamp())
def ensure_writable(self):
"""If the current "open" db.change has 1 million revisions,
close it and start a new one.
"""
# Currently open change is full.
# Close it.
if self.MAX_REVS_PER_CHANGELIST <= self.rev_ct:
self.close()
self._open()
self.rev_ct += 1
def _open(self):
"""Create a new db.change changelist record which we'll use as a
container for future db.rev file revisions.
"""
self.change_num += 1
self.date_p4_dt = _now_p4_dt(self.ctx)
self.rev_ct = 0
def close(self):
"""Write the currently "open" db.change changelist record to
the zip archive, then "close" it so no more revisions can use
it as a container.
Do not call if you haven't written any db.rev records to this change.
Perforce prohibits empty changelists.
"""
if not self.rev_ct:
return
self.p4unzip.change.jnl(self.db_change(), is_db_change = True)
self.p4unzip.change.jnl(self.db_desc())
# end class GitMirrorChange
# ----------------------------------------------------------------------------
# -- module-wide -------------------------------------------------------------
def _now_p4_dt(ctx):
"""Return the current time, as integer seconds since the epoch,
in the Perforce server's time zone.
"""
return (p4gf_time_zone.now_utc_dt()
.astimezone(p4gf_time_zone.server_tzinfo(ctx)))