From: Karthik Nayak <karthik.188@gmail.com>
To: git@vger.kernel.org
Cc: Karthik Nayak <karthik.188@gmail.com>,
ps@pks.im, jltobler@gmail.com, phillip.wood123@gmail.com
Subject: [PATCH v2 6/7] refs: implement partial reference transaction support
Date: Tue, 25 Feb 2025 10:29:09 +0100 [thread overview]
Message-ID: <20250225-245-partially-atomic-ref-updates-v2-6-cfa3236895d7@gmail.com> (raw)
In-Reply-To: <20250225-245-partially-atomic-ref-updates-v2-0-cfa3236895d7@gmail.com>
Git's reference transactions are all-or-nothing: either all updates
succeed, or none do. While this atomic behavior is generally desirable,
it can be suboptimal especially when using the reftable backend, where
batching multiple reference updates into a single transaction is more
efficient than performing them sequentially.
Introduce partial transaction support with a new flag,
'REF_TRANSACTION_ALLOW_PARTIAL'. When enabled, this flag allows
individual reference updates that would typically cause the entire
transaction to fail due to non-system-related errors to be marked as
rejected while permitting other updates to proceed. Non-system-related
errors include issues caused by user-provided input values, whereas
system-related errors, such as I/O failures or memory issues, continue
to result in a full transaction failure. This approach enhances
flexibility while preserving transactional integrity where necessary.
The implementation introduces several key components:
- Add 'rejection_err' field to struct `ref_update` to track failed
updates with failure reason.
- Modify reference backends (files, packed, reftable) to handle
partial transactions by using `ref_transaction_set_rejected()`
instead of failing the entire transaction when
`REF_TRANSACTION_ALLOW_PARTIAL` is set.
- Add `ref_transaction_for_each_rejected_update()` to let callers
examine which updates were rejected and why.
This foundational change enables partial transaction support throughout
the reference subsystem. The next commit will expose this capability to
users by adding a `--allow-partial` flag to 'git-update-ref(1)',
providing both a user-facing feature and a testable implementation.
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
refs.c | 31 +++++++++++++++++++++++++++++++
refs.h | 22 ++++++++++++++++++++++
refs/files-backend.c | 12 +++++++++++-
refs/packed-backend.c | 30 ++++++++++++++++++++++++++++--
refs/refs-internal.h | 13 +++++++++++++
refs/reftable-backend.c | 12 +++++++++++-
6 files changed, 116 insertions(+), 4 deletions(-)
diff --git a/refs.c b/refs.c
index f989a46a5a..243c09c368 100644
--- a/refs.c
+++ b/refs.c
@@ -1211,6 +1211,15 @@ void ref_transaction_free(struct ref_transaction *transaction)
free(transaction);
}
+void ref_transaction_set_rejected(struct ref_transaction *transaction,
+ size_t update_idx,
+ enum transaction_error err)
+{
+ if (update_idx >= transaction->nr)
+ BUG("trying to set rejection on invalid update index");
+ transaction->updates[update_idx]->rejection_err = err;
+}
+
struct ref_update *ref_transaction_add_update(
struct ref_transaction *transaction,
const char *refname, unsigned int flags,
@@ -1236,6 +1245,7 @@ struct ref_update *ref_transaction_add_update(
transaction->updates[transaction->nr++] = update;
update->flags = flags;
+ update->rejection_err = TRANSACTION_OK;
update->new_target = xstrdup_or_null(new_target);
update->old_target = xstrdup_or_null(old_target);
@@ -2726,6 +2736,27 @@ void ref_transaction_for_each_queued_update(struct ref_transaction *transaction,
}
}
+void ref_transaction_for_each_rejected_update(struct ref_transaction *transaction,
+ ref_transaction_for_each_rejected_update_fn cb,
+ void *cb_data)
+{
+ if (!(transaction->flags & REF_TRANSACTION_ALLOW_PARTIAL))
+ return;
+
+ for (size_t i = 0; i < transaction->nr; i++) {
+ struct ref_update *update = transaction->updates[i];
+
+ if (!update->rejection_err)
+ continue;
+
+ cb(update->refname,
+ (update->flags & REF_HAVE_OLD) ? &update->old_oid : NULL,
+ (update->flags & REF_HAVE_NEW) ? &update->new_oid : NULL,
+ update->old_target, update->new_target,
+ update->rejection_err, cb_data);
+ }
+}
+
int refs_delete_refs(struct ref_store *refs, const char *logmsg,
struct string_list *refnames, unsigned int flags)
{
diff --git a/refs.h b/refs.h
index 8e9ead174c..e4a6a8218f 100644
--- a/refs.h
+++ b/refs.h
@@ -675,6 +675,13 @@ enum ref_transaction_flag {
* either be absent or null_oid.
*/
REF_TRANSACTION_FLAG_INITIAL = (1 << 0),
+
+ /*
+ * The transaction mechanism by default fails all updates if any conflict
+ * is detected. This flag allows transactions to partially apply updates
+ * while rejecting updates which do not match the expected state.
+ */
+ REF_TRANSACTION_ALLOW_PARTIAL = (1 << 1),
};
/*
@@ -905,6 +912,21 @@ void ref_transaction_for_each_queued_update(struct ref_transaction *transaction,
ref_transaction_for_each_queued_update_fn cb,
void *cb_data);
+/*
+ * Execute the given callback function for each of the reference updates which
+ * have been rejected in the given transaction.
+ */
+typedef void ref_transaction_for_each_rejected_update_fn(const char *refname,
+ const struct object_id *old_oid,
+ const struct object_id *new_oid,
+ const char *old_target,
+ const char *new_target,
+ enum transaction_error err,
+ void *cb_data);
+void ref_transaction_for_each_rejected_update(struct ref_transaction *transaction,
+ ref_transaction_for_each_rejected_update_fn cb,
+ void *cb_data);
+
/*
* Free `*transaction` and all associated data.
*/
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 3b0adf8bb2..d0a53c9ace 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2851,8 +2851,18 @@ static int files_transaction_prepare(struct ref_store *ref_store,
ret = lock_ref_for_update(refs, update, transaction,
head_ref, &refnames_to_check,
err);
- if (ret)
+ if (ret) {
+ if (transaction->flags & REF_TRANSACTION_ALLOW_PARTIAL &&
+ ret != TRANSACTION_GENERIC_ERROR) {
+ ref_transaction_set_rejected(transaction, i, ret);
+
+ strbuf_setlen(err, 0);
+ ret = TRANSACTION_OK;
+
+ continue;
+ }
goto cleanup;
+ }
if (update->flags & REF_DELETING &&
!(update->flags & REF_LOG_ONLY) &&
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 75e1ebf67d..0857204213 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1324,10 +1324,11 @@ static int packed_ref_store_remove_on_disk(struct ref_store *ref_store,
* remain locked when it is done.
*/
static enum transaction_error write_with_updates(struct packed_ref_store *refs,
- struct string_list *updates,
+ struct ref_transaction *transaction,
struct strbuf *err)
{
enum transaction_error ret = TRANSACTION_GENERIC_ERROR;
+ struct string_list *updates = &transaction->refnames;
struct ref_iterator *iter = NULL;
size_t i;
int ok;
@@ -1408,6 +1409,14 @@ static enum transaction_error write_with_updates(struct packed_ref_store *refs,
"reference already exists",
update->refname);
ret = TRANSACTION_CREATE_EXISTS;
+
+ if (transaction->flags & REF_TRANSACTION_ALLOW_PARTIAL) {
+ ref_transaction_set_rejected(transaction, i, ret);
+ strbuf_setlen(err, 0);
+ ret = 0;
+ continue;
+ }
+
goto error;
} else if (!oideq(&update->old_oid, iter->oid)) {
strbuf_addf(err, "cannot update ref '%s': "
@@ -1416,6 +1425,14 @@ static enum transaction_error write_with_updates(struct packed_ref_store *refs,
oid_to_hex(iter->oid),
oid_to_hex(&update->old_oid));
ret = TRANSACTION_INCORRECT_OLD_VALUE;
+
+ if (transaction->flags & REF_TRANSACTION_ALLOW_PARTIAL) {
+ ref_transaction_set_rejected(transaction, i, ret);
+ strbuf_setlen(err, 0);
+ ret = 0;
+ continue;
+ }
+
goto error;
}
}
@@ -1453,6 +1470,14 @@ static enum transaction_error write_with_updates(struct packed_ref_store *refs,
update->refname,
oid_to_hex(&update->old_oid));
return TRANSACTION_NONEXISTENT_REF;
+
+ if (transaction->flags & REF_TRANSACTION_ALLOW_PARTIAL) {
+ ref_transaction_set_rejected(transaction, i, ret);
+ strbuf_setlen(err, 0);
+ ret = 0;
+ continue;
+ }
+
goto error;
}
}
@@ -1518,6 +1543,7 @@ static enum transaction_error write_with_updates(struct packed_ref_store *refs,
write_error:
strbuf_addf(err, "error writing to %s: %s",
get_tempfile_path(refs->tempfile), strerror(errno));
+ ret = TRANSACTION_GENERIC_ERROR;
error:
ref_iterator_free(iter);
@@ -1676,7 +1702,7 @@ static int packed_transaction_prepare(struct ref_store *ref_store,
data->own_lock = 1;
}
- ret = write_with_updates(refs, &transaction->refnames, err);
+ ret = write_with_updates(refs, transaction, err);
if (ret)
goto failure;
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index c97045fbed..7196f2d880 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -123,6 +123,11 @@ struct ref_update {
*/
uint64_t index;
+ /*
+ * Used in partial transactions to mark if a given update was rejected.
+ */
+ enum transaction_error rejection_err;
+
/*
* If this ref_update was split off of a symref update via
* split_symref_update(), then this member points at that
@@ -142,6 +147,14 @@ int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,
struct object_id *oid, struct strbuf *referent,
unsigned int *type, int *failure_errno);
+/*
+ * Mark a given update as rejected with a given reason. To be used in conjuction
+ * with the `REF_TRANSACTION_ALLOW_PARTIAL` flag to allow partial transactions.
+ */
+void ref_transaction_set_rejected(struct ref_transaction *transaction,
+ size_t update_idx,
+ enum transaction_error err);
+
/*
* Add a ref_update with the specified properties to transaction, and
* return a pointer to the new object. This function does not verify
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index e1fd9c2de2..83cf8d582b 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1374,8 +1374,18 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
transaction->updates[i],
&refnames_to_check, head_type,
&head_referent, &referent, err);
- if (ret)
+ if (ret) {
+ if (transaction->flags & REF_TRANSACTION_ALLOW_PARTIAL &&
+ ret != TRANSACTION_GENERIC_ERROR) {
+ ref_transaction_set_rejected(transaction, i, ret);
+
+ strbuf_setlen(err, 0);
+ ret = TRANSACTION_OK;
+
+ continue;
+ }
goto done;
+ }
}
string_list_sort(&refnames_to_check);
--
2.47.2
next prev parent reply other threads:[~2025-02-25 9:29 UTC|newest]
Thread overview: 147+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-02-07 7:34 [PATCH 0/6] refs: introduce support for partial reference transactions Karthik Nayak
2025-02-07 7:34 ` [PATCH 1/6] refs/files: remove duplicate check in `split_symref_update()` Karthik Nayak
2025-02-07 16:12 ` Patrick Steinhardt
2025-02-11 6:35 ` Karthik Nayak
2025-02-07 7:34 ` [PATCH 2/6] refs: move duplicate refname update check to generic layer Karthik Nayak
2025-02-07 16:12 ` Patrick Steinhardt
2025-02-11 10:33 ` Karthik Nayak
2025-02-07 7:34 ` [PATCH 3/6] refs/files: remove duplicate duplicates check Karthik Nayak
2025-02-07 16:12 ` Patrick Steinhardt
2025-02-07 7:34 ` [PATCH 4/6] refs/reftable: extract code from the transaction preparation Karthik Nayak
2025-02-07 7:34 ` [PATCH 5/6] refs: implement partial reference transaction support Karthik Nayak
2025-02-07 16:12 ` Patrick Steinhardt
2025-02-21 10:33 ` Karthik Nayak
2025-02-07 7:34 ` [PATCH 6/6] update-ref: add --allow-partial flag for stdin mode Karthik Nayak
2025-02-07 16:12 ` Patrick Steinhardt
2025-02-21 11:45 ` Karthik Nayak
2025-02-11 17:03 ` [PATCH 0/6] refs: introduce support for partial reference transactions Phillip Wood
2025-02-11 17:40 ` Phillip Wood
2025-02-12 12:36 ` Karthik Nayak
2025-02-12 12:34 ` Karthik Nayak
2025-02-19 14:34 ` Phillip Wood
2025-02-19 15:10 ` Patrick Steinhardt
2025-02-21 11:50 ` Karthik Nayak
2025-02-25 9:29 ` [PATCH v2 0/7] " Karthik Nayak
2025-02-25 9:29 ` [PATCH v2 1/7] refs/files: remove redundant check in split_symref_update() Karthik Nayak
2025-02-25 9:29 ` [PATCH v2 2/7] refs: move duplicate refname update check to generic layer Karthik Nayak
2025-02-25 9:29 ` [PATCH v2 3/7] refs/files: remove duplicate duplicates check Karthik Nayak
2025-02-25 9:29 ` [PATCH v2 4/7] refs/reftable: extract code from the transaction preparation Karthik Nayak
2025-02-25 9:29 ` [PATCH v2 5/7] refs: introduce enum-based transaction error types Karthik Nayak
2025-02-25 11:08 ` Patrick Steinhardt
2025-03-03 20:12 ` Karthik Nayak
2025-02-25 9:29 ` Karthik Nayak [this message]
2025-02-25 11:07 ` [PATCH v2 6/7] refs: implement partial reference transaction support Patrick Steinhardt
2025-03-03 20:17 ` Karthik Nayak
2025-02-25 14:57 ` Phillip Wood
2025-03-03 20:21 ` Karthik Nayak
2025-03-04 10:31 ` Phillip Wood
2025-03-05 14:20 ` Karthik Nayak
2025-02-25 9:29 ` [PATCH v2 7/7] update-ref: add --allow-partial flag for stdin mode Karthik Nayak
2025-02-25 11:08 ` Patrick Steinhardt
2025-03-03 20:22 ` Karthik Nayak
2025-02-25 14:59 ` Phillip Wood
2025-03-03 20:34 ` Karthik Nayak
2025-03-05 17:38 ` [PATCH v3 0/8] refs: introduce support for partial reference transactions Karthik Nayak
2025-03-05 17:38 ` [PATCH v3 1/8] refs/files: remove redundant check in split_symref_update() Karthik Nayak
2025-03-05 21:20 ` Junio C Hamano
2025-03-06 9:13 ` Karthik Nayak
2025-03-05 17:38 ` [PATCH v3 2/8] refs: move duplicate refname update check to generic layer Karthik Nayak
2025-03-05 21:56 ` Junio C Hamano
2025-03-06 9:46 ` Karthik Nayak
2025-03-05 17:38 ` [PATCH v3 3/8] refs/files: remove duplicate duplicates check Karthik Nayak
2025-03-05 17:38 ` [PATCH v3 4/8] refs/reftable: extract code from the transaction preparation Karthik Nayak
2025-03-05 17:39 ` [PATCH v3 5/8] refs: introduce enum-based transaction error types Karthik Nayak
2025-03-05 17:39 ` [PATCH v3 6/8] refs: implement partial reference transaction support Karthik Nayak
2025-03-07 19:50 ` Jeff King
2025-03-07 20:46 ` Junio C Hamano
2025-03-07 20:48 ` Junio C Hamano
2025-03-07 21:05 ` Karthik Nayak
2025-03-07 22:54 ` [PATCH] config.mak.dev: enable -Wunreachable-code Jeff King
2025-03-07 23:28 ` Junio C Hamano
2025-03-08 3:23 ` Jeff King
2025-03-10 15:40 ` Junio C Hamano
2025-03-10 16:04 ` Jeff King
2025-03-10 18:50 ` Junio C Hamano
2025-03-14 16:10 ` Jeff King
2025-03-14 16:13 ` Jeff King
2025-03-14 17:27 ` Junio C Hamano
2025-03-14 17:40 ` Junio C Hamano
2025-03-14 17:43 ` Patrick Steinhardt
2025-03-14 18:53 ` Jeff King
2025-03-14 19:50 ` Junio C Hamano
2025-03-14 17:15 ` Junio C Hamano
2025-06-03 21:29 ` Mike Hommey
2025-06-03 22:07 ` Junio C Hamano
2025-06-03 22:37 ` Mike Hommey
2025-06-03 23:08 ` Mike Hommey
2025-03-14 21:09 ` [PATCH v2 0/3] -Wunreachable-code Junio C Hamano
2025-03-14 21:09 ` [PATCH v2 1/3] config.mak.dev: enable -Wunreachable-code Junio C Hamano
2025-03-14 21:09 ` [PATCH v2 2/3] run-command: use errno to check for sigfillset() error Junio C Hamano
2025-03-17 21:30 ` Taylor Blau
2025-03-17 23:12 ` Junio C Hamano
2025-03-18 0:36 ` Junio C Hamano
2025-03-14 21:09 ` [PATCH v2 3/3] git-compat-util: add NOT_A_CONST macro and use it in atfork_prepare() Junio C Hamano
2025-03-14 22:29 ` Junio C Hamano
2025-03-17 18:00 ` Jeff King
2025-03-17 23:53 ` [PATCH v3 0/3] -Wunreachable-code Junio C Hamano
2025-03-17 23:53 ` [PATCH v3 1/3] run-command: use errno to check for sigfillset() error Junio C Hamano
2025-03-17 23:53 ` [PATCH v3 2/3] git-compat-util: add NOT_CONSTANT macro and use it in atfork_prepare() Junio C Hamano
2025-03-18 0:20 ` Jeff King
2025-03-18 0:28 ` Junio C Hamano
2025-03-18 22:04 ` Calvin Wan
2025-03-18 22:26 ` Calvin Wan
2025-03-18 23:55 ` Junio C Hamano
2025-03-17 23:53 ` [PATCH v3 3/3] config.mak.dev: enable -Wunreachable-code Junio C Hamano
2025-03-18 0:18 ` [PATCH v3 0/3] -Wunreachable-code Jeff King
2025-03-07 21:02 ` [PATCH v3 6/8] refs: implement partial reference transaction support Karthik Nayak
2025-03-07 19:57 ` Jeff King
2025-03-07 21:07 ` Karthik Nayak
2025-03-05 17:39 ` [PATCH v3 7/8] refs: support partial update rejections during F/D checks Karthik Nayak
2025-03-05 17:39 ` [PATCH v3 8/8] update-ref: add --allow-partial flag for stdin mode Karthik Nayak
2025-03-05 19:28 ` [PATCH v3 0/8] refs: introduce support for partial reference transactions Junio C Hamano
2025-03-06 9:06 ` Karthik Nayak
2025-03-20 11:43 ` [PATCH v4 0/8] refs: introduce support for batched reference updates Karthik Nayak
2025-03-20 11:43 ` [PATCH v4 1/8] refs/files: remove redundant check in split_symref_update() Karthik Nayak
2025-03-20 11:43 ` [PATCH v4 2/8] refs: move duplicate refname update check to generic layer Karthik Nayak
2025-03-20 11:43 ` [PATCH v4 3/8] refs/files: remove duplicate duplicates check Karthik Nayak
2025-03-20 11:43 ` [PATCH v4 4/8] refs/reftable: extract code from the transaction preparation Karthik Nayak
2025-03-20 11:44 ` [PATCH v4 5/8] refs: introduce enum-based transaction error types Karthik Nayak
2025-03-20 20:26 ` Patrick Steinhardt
2025-03-24 14:50 ` Karthik Nayak
2025-03-25 12:31 ` Patrick Steinhardt
2025-03-20 11:44 ` [PATCH v4 6/8] refs: implement batch reference update support Karthik Nayak
2025-03-20 20:26 ` Patrick Steinhardt
2025-03-24 14:54 ` Karthik Nayak
2025-03-20 11:44 ` [PATCH v4 7/8] refs: support rejection in batch updates during F/D checks Karthik Nayak
2025-03-24 13:08 ` Patrick Steinhardt
2025-03-24 17:48 ` Karthik Nayak
2025-03-25 12:31 ` Patrick Steinhardt
2025-03-20 11:44 ` [PATCH v4 8/8] update-ref: add --batch-updates flag for stdin mode Karthik Nayak
2025-03-24 13:08 ` Patrick Steinhardt
2025-03-24 17:51 ` Karthik Nayak
2025-03-27 11:13 ` [PATCH v5 0/8] refs: introduce support for batched reference updates Karthik Nayak
2025-03-27 11:13 ` [PATCH v5 1/8] refs/files: remove redundant check in split_symref_update() Karthik Nayak
2025-03-27 11:13 ` [PATCH v5 2/8] refs: move duplicate refname update check to generic layer Karthik Nayak
2025-03-27 11:13 ` [PATCH v5 3/8] refs/files: remove duplicate duplicates check Karthik Nayak
2025-03-27 11:13 ` [PATCH v5 4/8] refs/reftable: extract code from the transaction preparation Karthik Nayak
2025-03-27 11:13 ` [PATCH v5 5/8] refs: introduce enum-based transaction error types Karthik Nayak
2025-03-27 11:13 ` [PATCH v5 6/8] refs: implement batch reference update support Karthik Nayak
2025-03-27 11:13 ` [PATCH v5 7/8] refs: support rejection in batch updates during F/D checks Karthik Nayak
2025-03-27 11:13 ` [PATCH v5 8/8] update-ref: add --batch-updates flag for stdin mode Karthik Nayak
2025-03-28 13:00 ` Jean-Noël AVILA
2025-03-29 16:36 ` Junio C Hamano
2025-03-29 18:18 ` Karthik Nayak
2025-03-28 9:24 ` [PATCH v5 0/8] refs: introduce support for batched reference updates Patrick Steinhardt
2025-04-08 8:51 ` [PATCH v6 " Karthik Nayak
2025-04-08 8:51 ` [PATCH v6 1/8] refs/files: remove redundant check in split_symref_update() Karthik Nayak
2025-04-08 8:51 ` [PATCH v6 2/8] refs: move duplicate refname update check to generic layer Karthik Nayak
2025-04-08 8:51 ` [PATCH v6 3/8] refs/files: remove duplicate duplicates check Karthik Nayak
2025-04-08 8:51 ` [PATCH v6 4/8] refs/reftable: extract code from the transaction preparation Karthik Nayak
2025-04-08 8:51 ` [PATCH v6 5/8] refs: introduce enum-based transaction error types Karthik Nayak
2025-04-08 8:51 ` [PATCH v6 6/8] refs: implement batch reference update support Karthik Nayak
2025-04-08 8:51 ` [PATCH v6 7/8] refs: support rejection in batch updates during F/D checks Karthik Nayak
2025-04-08 8:51 ` [PATCH v6 8/8] update-ref: add --batch-updates flag for stdin mode Karthik Nayak
2025-04-08 15:02 ` Junio C Hamano
2025-04-08 15:26 ` Karthik Nayak
2025-04-08 17:37 ` Junio C Hamano
2025-04-10 11:23 ` Karthik Nayak
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250225-245-partially-atomic-ref-updates-v2-6-cfa3236895d7@gmail.com \
--to=karthik.188@gmail.com \
--cc=git@vger.kernel.org \
--cc=jltobler@gmail.com \
--cc=phillip.wood123@gmail.com \
--cc=ps@pks.im \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).