Skip to content

Commit a774047

Browse files
🐛 Send out mail only once every force interval
1 parent 0e037a4 commit a774047

File tree

4 files changed

+49
-27
lines changed

4 files changed

+49
-27
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
1414

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

1821
## Changed
1922

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ gen-docker-dev:python-package
109109
(echo "### THIS FILE WAS AUTOGENERATED, CHANGES WILL BE LOST ###" && \
110110
sed -e 's/^##DEVONLY## *//' -e '/##PRODONLY##$$/d' \
111111
< autoblockchainify/$$i ) > autoblockchainify-dev/$$i; done
112-
for i in health.sh; do \
112+
for i in run-autoblockchainify.sh health.sh; do \
113113
(head -1 autoblockchainify/$$i && \
114114
echo "### THIS FILE WAS AUTOGENERATED, CHANGES WILL BE LOST ###" && \
115115
sed -e 's/^##DEVONLY## *//' -e '/##PRODONLY##$$/d' \

autoblockchainify/commit.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020

2121
# Committing to git and obtaining timestamps
2222

23-
import datetime
23+
from datetime import datetime, timezone
2424
import logging as _logging
25+
from pathlib import Path
2526
import subprocess
2627
import sys
2728
import threading
@@ -53,12 +54,15 @@ def cross_timestamp(repo, branch, server):
5354
% (branch, server))
5455

5556

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

6367

6468
def pending_merge(repo):
@@ -69,7 +73,7 @@ def pending_merge(repo):
6973
def commit_current_state(repo):
7074
"""Force a commit; will be called only if a commit has to be made.
7175
I.e., if there really are changes or the force duration has expired."""
72-
now = datetime.datetime.now(datetime.timezone.utc)
76+
now = datetime.now(timezone.utc)
7377
nowstr = now.strftime('%Y-%m-%d %H:%M:%S UTC')
7478
subprocess.run(['git', 'add', '.'],
7579
cwd=repo, check=True)
@@ -85,9 +89,9 @@ def head_older_than(repo, duration):
8589
r = git.Repository(repo)
8690
if r.head_is_unborn:
8791
return False
88-
now = datetime.datetime.utcnow()
89-
if datetime.datetime.utcfromtimestamp(r.head.peel().commit_time) + duration < now:
90-
return True
92+
now = datetime.utcnow()
93+
return datetime.utcfromtimestamp(
94+
r.head.peel().commit_time) + duration < now
9195

9296

9397
def do_commit():
@@ -108,18 +112,18 @@ def do_commit():
108112
# Allow 5% of an interval tolerance, such that small timing differences
109113
# will not lead to lengthening the duration by one commit_interval
110114
force_interval = (autoblockchainify.config.arg.commit_interval
111-
* (autoblockchainify.config.arg.force_after_intervals - 0.05))
115+
* (autoblockchainify.config.arg.force_after_intervals - 0.05))
112116
try:
113117
repo = autoblockchainify.config.arg.repository
114118
# If a merge (a manual process on the repository) is detected,
115119
# try to not interfere with the manual process and wait for the
116120
# next forced update
117-
if ((has_changes(repo) and not pending_merge(repo))
121+
if ((has_user_changes(repo) and not pending_merge(repo))
118122
or head_older_than(repo, force_interval)):
119123
# 1. Commit
120124
commit_current_state(repo)
121125

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

135-
# 4. Timestamp by mail (asynchronous)
139+
# 4. Timestamp by mail (asynchronously)
136140
if autoblockchainify.config.arg.stamper_own_address:
137-
logging.info("cross-timestamping by mail")
138-
autoblockchainify.mail.async_email_timestamp()
141+
autoblockchainify.mail.async_email_timestamp(wait=force_interval)
139142

140-
logging.info("do_commit done")
143+
logging.info("do_commit done")
141144
except Exception as e:
142145
logging.error("Unhandled exception in do_commit() thread: %s: %s" %
143-
(e, ''.join(traceback.format_tb(sys.exc_info()[2]))))
146+
(e, ''.join(traceback.format_tb(sys.exc_info()[2]))))
144147

145148

146149
def loop():

autoblockchainify/mail.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,19 @@ def wait_for_receive(logfile):
314314
logging.error("No response received, giving up")
315315

316316

317-
def async_email_timestamp(resume=False):
317+
def not_modified_in(file, wait):
318+
"""Has `logfile` not been modified in ~`wait` seconds?
319+
Non-existent file is considered to *not* fulfill this."""
320+
try:
321+
stat = file.stat()
322+
mtime = datetime.utcfromtimestamp(stat.st_mtime)
323+
now = datetime.utcnow()
324+
return mtime + wait < now
325+
except FileNotFoundError:
326+
return False
327+
328+
329+
def async_email_timestamp(resume=False, wait=None):
318330
"""If called with `resume=True`, tries to resume waiting for the mail"""
319331
path = autoblockchainify.config.arg.repository
320332
repo = git.Repository(path)
@@ -326,6 +338,7 @@ def async_email_timestamp(resume=False):
326338
return
327339
head = repo.head
328340
logfile = Path(path, 'pgp-timestamp.tmp')
341+
sigfile = Path(path, 'pgp-timestamp.sig')
329342
if resume:
330343
if not logfile.is_file():
331344
logging.info("Not resuming mail timestamp: No pending mail reply")
@@ -336,12 +349,15 @@ def async_email_timestamp(resume=False):
336349
logging.info("Not resuming mail timestamp: No revision info")
337350
return
338351
else: # Fresh request
339-
new_rev = ("git commit %s\nTimestamp requested at %s\n" %
340-
(head.target.hex,
341-
strftime("%Y-%m-%d %H:%M:%S UTC", gmtime())))
342-
with serialize_create:
343-
with logfile.open('w') as f:
344-
f.write(new_rev)
345-
send(new_rev)
346-
threading.Thread(target=wait_for_receive, args=(logfile,),
347-
daemon=True).start()
352+
# No recent attempts or results for mail timestamping
353+
if (not sigfile.is_file() or not_modified_in(logfile, wait)
354+
or not_modified_in(sigfile, wait)):
355+
new_rev = ("git commit %s\nTimestamp requested at %s\n" %
356+
(head.target.hex,
357+
strftime("%Y-%m-%d %H:%M:%S UTC", gmtime())))
358+
with serialize_create:
359+
with logfile.open('w') as f:
360+
f.write(new_rev)
361+
send(new_rev)
362+
threading.Thread(target=wait_for_receive, args=(logfile,),
363+
daemon=True).start()

0 commit comments

Comments
 (0)