Skip to content

Commit

Permalink
🐛 Send out mail only once every force interval
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcelWaldvogel committed Sep 29, 2020
1 parent 0e037a4 commit a774047
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 27 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

## Fixed
- Handle that `.gnupg` also is in the volume now
- Send out mail only every force interval. Also, do not trigger immediate
addition of a mail reply only, avoiding double commits/timestamps every
force interval on idle repositories.

## Changed

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ gen-docker-dev:python-package
(echo "### THIS FILE WAS AUTOGENERATED, CHANGES WILL BE LOST ###" && \
sed -e 's/^##DEVONLY## *//' -e '/##PRODONLY##$$/d' \
< autoblockchainify/$$i ) > autoblockchainify-dev/$$i; done
for i in health.sh; do \
for i in run-autoblockchainify.sh health.sh; do \
(head -1 autoblockchainify/$$i && \
echo "### THIS FILE WAS AUTOGENERATED, CHANGES WILL BE LOST ###" && \
sed -e 's/^##DEVONLY## *//' -e '/##PRODONLY##$$/d' \
Expand Down
35 changes: 19 additions & 16 deletions autoblockchainify/commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@

# Committing to git and obtaining timestamps

import datetime
from datetime import datetime, timezone
import logging as _logging
from pathlib import Path
import subprocess
import sys
import threading
Expand Down Expand Up @@ -53,12 +54,15 @@ def cross_timestamp(repo, branch, server):
% (branch, server))


def has_changes(repo):
def has_user_changes(repo):
"""Check whether there are uncommitted changes, i.e., whether
`git status -z` has any output."""
`git status -z` has any output. A modification of only `pgp-timestamp.sig`
is ignored, as it is neither necessary nor desirable to trigger on it:
(a) our own timestamp is not really needed on it and
(b) it would cause an unnecessary second timestamp per idle force period."""
ret = subprocess.run(['git', 'status', '-z'],
cwd=repo, capture_output=True, check=True)
return len(ret.stdout) > 0
return len(ret.stdout) > 0 and ret.stdout != b' M pgp-timestamp.sig\0'


def pending_merge(repo):
Expand All @@ -69,7 +73,7 @@ def pending_merge(repo):
def commit_current_state(repo):
"""Force a commit; will be called only if a commit has to be made.
I.e., if there really are changes or the force duration has expired."""
now = datetime.datetime.now(datetime.timezone.utc)
now = datetime.now(timezone.utc)
nowstr = now.strftime('%Y-%m-%d %H:%M:%S UTC')
subprocess.run(['git', 'add', '.'],
cwd=repo, check=True)
Expand All @@ -85,9 +89,9 @@ def head_older_than(repo, duration):
r = git.Repository(repo)
if r.head_is_unborn:
return False
now = datetime.datetime.utcnow()
if datetime.datetime.utcfromtimestamp(r.head.peel().commit_time) + duration < now:
return True
now = datetime.utcnow()
return datetime.utcfromtimestamp(
r.head.peel().commit_time) + duration < now


def do_commit():
Expand All @@ -108,18 +112,18 @@ def do_commit():
# Allow 5% of an interval tolerance, such that small timing differences
# will not lead to lengthening the duration by one commit_interval
force_interval = (autoblockchainify.config.arg.commit_interval
* (autoblockchainify.config.arg.force_after_intervals - 0.05))
* (autoblockchainify.config.arg.force_after_intervals - 0.05))
try:
repo = autoblockchainify.config.arg.repository
# If a merge (a manual process on the repository) is detected,
# try to not interfere with the manual process and wait for the
# next forced update
if ((has_changes(repo) and not pending_merge(repo))
if ((has_user_changes(repo) and not pending_merge(repo))
or head_older_than(repo, force_interval)):
# 1. Commit
commit_current_state(repo)

# 2. Timestamp using Zeitgitter
# 2. Timestamp (synchronously) using Zeitgitter
repositories = autoblockchainify.config.arg.push_repository
branches = autoblockchainify.config.arg.push_branch
for r in autoblockchainify.config.arg.zeitgitter_servers:
Expand All @@ -132,15 +136,14 @@ def do_commit():
logging.info("Pushing upstream to %s" % r)
push_upstream(repo, r, branches)

# 4. Timestamp by mail (asynchronous)
# 4. Timestamp by mail (asynchronously)
if autoblockchainify.config.arg.stamper_own_address:
logging.info("cross-timestamping by mail")
autoblockchainify.mail.async_email_timestamp()
autoblockchainify.mail.async_email_timestamp(wait=force_interval)

logging.info("do_commit done")
logging.info("do_commit done")
except Exception as e:
logging.error("Unhandled exception in do_commit() thread: %s: %s" %
(e, ''.join(traceback.format_tb(sys.exc_info()[2]))))
(e, ''.join(traceback.format_tb(sys.exc_info()[2]))))


def loop():
Expand Down
36 changes: 26 additions & 10 deletions autoblockchainify/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,19 @@ def wait_for_receive(logfile):
logging.error("No response received, giving up")


def async_email_timestamp(resume=False):
def not_modified_in(file, wait):
"""Has `logfile` not been modified in ~`wait` seconds?
Non-existent file is considered to *not* fulfill this."""
try:
stat = file.stat()
mtime = datetime.utcfromtimestamp(stat.st_mtime)
now = datetime.utcnow()
return mtime + wait < now
except FileNotFoundError:
return False


def async_email_timestamp(resume=False, wait=None):
"""If called with `resume=True`, tries to resume waiting for the mail"""
path = autoblockchainify.config.arg.repository
repo = git.Repository(path)
Expand All @@ -326,6 +338,7 @@ def async_email_timestamp(resume=False):
return
head = repo.head
logfile = Path(path, 'pgp-timestamp.tmp')
sigfile = Path(path, 'pgp-timestamp.sig')
if resume:
if not logfile.is_file():
logging.info("Not resuming mail timestamp: No pending mail reply")
Expand All @@ -336,12 +349,15 @@ def async_email_timestamp(resume=False):
logging.info("Not resuming mail timestamp: No revision info")
return
else: # Fresh request
new_rev = ("git commit %s\nTimestamp requested at %s\n" %
(head.target.hex,
strftime("%Y-%m-%d %H:%M:%S UTC", gmtime())))
with serialize_create:
with logfile.open('w') as f:
f.write(new_rev)
send(new_rev)
threading.Thread(target=wait_for_receive, args=(logfile,),
daemon=True).start()
# No recent attempts or results for mail timestamping
if (not sigfile.is_file() or not_modified_in(logfile, wait)
or not_modified_in(sigfile, wait)):
new_rev = ("git commit %s\nTimestamp requested at %s\n" %
(head.target.hex,
strftime("%Y-%m-%d %H:%M:%S UTC", gmtime())))
with serialize_create:
with logfile.open('w') as f:
f.write(new_rev)
send(new_rev)
threading.Thread(target=wait_for_receive, args=(logfile,),
daemon=True).start()

0 comments on commit a774047

Please sign in to comment.