CHANGELOG
=========

v0.9.0 (2026-08-12)
-------------------
- Make cancellation operation-scoped. Every public entry point now runs
  as one logical operation, and cancel_active() cancels only operations
  already in flight: each stays cancelled for the rest of its run (a
  batch aborts fully, even when the cancel lands between requests),
  while operations started afterwards proceed normally -- there is no
  sticky flag to reset. shutdown() is the new terminal state for
  application exit: it cancels everything in flight and refuses all new
  operations; is_shutdown reports it. cancel() remains as a deprecated
  alias for cancel_active(), and reset_cancel() is now a deprecated
  no-op. See MIGRATIONS.md for the 0.8 -> 0.9 migration guide.

v0.8.2 (2026-08-12)
-------------------
- Fix get_strict_thread() dropping an entire series when a message's
  In-Reply-To/References headers point at its own Message-ID. Such
  self-references are now filtered out for every message, instead of being
  treated as a parent, which previously caused a self-referencing cover
  letter with noparent set to seed the ignore set with its own msgid and
  silently discard all of its children.

v0.8.1 (2026-07-22)
-------------------
- Fix get_clean_msgid() destroying the message-ID when a msg-id header
  (In-Reply-To/References/Message-Id) also carries an RFC 2047 encoded-word
  comment, as Gnus emits. The ID is now extracted straight from the raw
  header value instead of via clean_header(), matching _clean_msgid_raw().

v0.8.0 (2026-06-11)
---------------------
- Add configurable request_timeout (default connect=5s, read=30s) applied
  to all outgoing HTTP requests, including the direct HEAD calls in
  _resolve_msgid_via_head() and validate(). Configurable via the
  `requesttimeout` git config key (single float or "connect,read").
- Add LoreNode.cancel() / reset_cancel() for thread-safe cancellation of
  in-flight requests. cancel() closes the owned session to interrupt a
  blocked socket read and raises the new OperationCancelledError on the next
  request boundary; it never fails over to another origin. Injected sessions
  are left untouched.

v0.7.1 (2026-04-08)
--------------------
- Add per-origin git config via [liblore "<origin>"] subsections.
  from_git_config() now looks for configuration in
  [liblore "https://lore.kernel.org"] (or whichever origin is passed)
  before falling back to the legacy [lore] section. The [lore]
  fallback only applies to lore.kernel.org URLs, so non-lore servers
  like subspace.kernel.org get independent mirror and probe settings
  without inheriting lore.kernel.org mirrors. Add new
  _get_subsection_config() helper that correctly handles dots in
  subsection names (URLs) by prefix-stripping instead of last-dot
  splitting.

v0.7.0 (2026-04-08)
--------------------
- Add public request() method to LoreNode for making arbitrary HTTP
  requests through the failover machinery. Downstream projects like
  korgalore can now route their own HTTP calls (e.g. manifest.js.gz
  fetches) through LoreNode's origin rotation and auto-probe support
  without reimplementing the failover logic. Always raises RemoteError
  when all origins are exhausted.

- Add read-only user_agent_plus property exposing the
  lore.useragentplus value from git config. Downstream projects can
  reuse this tracking identifier in their own user-agent strings (e.g.
  for git HTTP or lei CLI flags) without re-reading git config or
  accessing private attributes.

- Add read-only origins property returning a copy of all configured
  origins in their current probe order (fallbacks first, then
  canonical). After probe_origins() runs, the list reflects the
  fastest-first reordering. Useful for logging, diagnostics, and UI
  display of configured mirrors.

- Add read-only canonical_origin property returning the scheme://host
  origin extracted from the primary URL. Paired with origins, lets
  callers distinguish the configured primary from fallback mirrors and
  construct URLs suitable for the request() method.

v0.6.1 (2026-04-08)
--------------------
- Fix get_mbox_by_msgid() returning 404 when the configured URL does
  not include the correct list path.  On a 404, the method now falls
  back to a HEAD request against the bare server origin and follows
  the redirect to discover the canonical list path, then fetches the
  mbox from the resolved location.

v0.6.0 (2026-04-08)
--------------------
- Add URL-level failover via new fallback_urls constructor parameter.
  Fallback URLs are origin prefixes (scheme://host) that support full
  scheme rewriting, so local http:// mirrors and .onion endpoints work
  alongside https:// servers. On each request, origins are tried in
  order; connection errors, timeouts, and 5xx responses fall through
  to the next origin while 4xx responses are returned immediately.
  Cache keys always use the canonical URL regardless of which mirror
  served the response.

- Add concurrent origin probing via probe_origins(), which sends HEAD
  requests to /manifest.js.gz on all configured origins using
  ThreadPoolExecutor to find the fastest mirror. Unreachable origins
  are moved to the end rather than removed. Results are cached to
  cache_dir for probe_ttl seconds (default 3600). Pass nocache=True
  to force a live probe even when cached results exist. Set
  auto_probe=True to trigger probing transparently on the first
  request.

- Add LoreNode.from_git_config() classmethod that reads settings from
  the [lore] section of git config (repo, global, or system) via a
  single "git config -z --get-regexp" call. Supported keys:
  lore.fallback (multi-value), lore.autoprobe, lore.probetimeout,
  lore.probettl, lore.useragentplus. All keys are optional; missing
  git or any failure is silently ignored. Explicit keyword arguments
  take precedence over git config values.

- set_user_agent() now falls back to the lore.useragentplus value
  from git config when the caller does not pass an explicit plus=
  argument, so a single git config entry identifies the installation
  across all liblore-based tools.

v0.5.0 (2026-04-07)
--------------------
- Add optional filesystem caching to LoreNode via new cache_dir and
  cache_ttl constructor parameters. When cache_dir is set, raw mbox
  bytes from get_mbox_by_msgid(), get_mbox_by_query(), and
  get_message_by_msgid() are cached on disk with per-request TTL
  validation. Stale entries are cleaned up on read. Polling methods
  (get_thread_updates_since, _fetch_thread_since) are intentionally
  not cached. Pass nocache=True to any cached method to bypass the
  cache for a single call. Add clear_cache() for bulk invalidation.

- Add read-only url property to LoreNode for accessing the configured
  endpoint URL.

- Add full_threads parameter to get_mbox_by_query(),
  get_thread_by_query(), and batch_get_thread_by_query(). When set to
  True, the server expands results to include the full thread for
  every matching message (public-inbox t=1 parameter). This is needed
  by callers like b4 that search by patch-id or change-id and expect
  the complete surrounding thread, not just the matching messages.

v0.4.0 (2026-03-05)
--------------------
- Add split_mbox_as_bytes() and split_and_dedupe_as_bytes() for
  working with raw message bytes without invoking the email parser.
  The regular split_mbox() and split_and_dedupe() now wrap these
  functions, so the splitting logic is shared.

- The bytes-level deduplication extracts Message-ID and List-Id
  headers directly from raw bytes using lightweight line scanning,
  avoiding the cost of full message parsing.

v0.3.1 (2026-03-04)
--------------------
- Lower minimum Python version from 3.11 to 3.9. The codebase already
  uses ``from __future__ import annotations`` everywhere, so no code
  changes were required.

- Clean up unused imports in test_formatting.py.

v0.3.0 (2026-03-04)
--------------------
- Add email serialization utilities ported from b4/bugspray:
  format_addrs(), wrap_header(), get_msg_as_bytes(). These replace
  Python's buggy as_bytes() with battle-tested RFC 2047 header
  encoding and line wrapping.

- Add minimize_thread() for creating compact thread views by
  stripping excessive quoting, multi-level quotes, and non-essential
  headers. Messages containing diffs/diffstats are preserved as-is.
  Accepts an optional keep_headers parameter for customizing which
  headers to retain. Compliant email signatures are split off before
  processing so trailing quoted blocks before a signature are
  correctly recognised and dropped.

- Add optional message authentication (DKIM, DMARC, ARC) via the
  authheaders library. Enable with LoreNode(add_auth_headers=True).
  Install the optional dependency with: pip install liblore[auth].

- Add reduce_quote_context option to minimize_thread() for more
  aggressive quote reduction: long quoted blocks preceding a reply
  are trimmed to just the last paragraph, with earlier content
  replaced by a "> [... skip NN lines ...]" marker. Only kicks in
  when more than 5 lines would be skipped.

v0.2.0 (2026-03-03)
--------------------
- Fix thread update fetching to use the per-message search endpoint
  (/{msgid}/?x=m&q=dt:{since}..) instead of a global query with
  mid:{msgid}. This scopes the search to the correct thread and
  filters by Date header (dt:) instead of commit date (d:).

- Fix get_mbox_by_query() to POST 'x=m' as request body, ensuring
  the server returns mbox format.

- Add get_thread_updates_since() convenience method for checking
  whether a thread has new messages. Accepts a datetime object and
  returns an empty list when there are no updates, making it easy
  to poll for changes. Uses the rt: (Received header) search prefix
  with epoch timestamps for reliable server-side date filtering.

- Refactor get_thread_by_msgid() to use GET /{msgid}/t.mbox.gz for
  full thread fetches (no since parameter), avoiding unnecessary
  query overhead.

v0.1.0 (2025-08-15)
--------------------
- Initial release.
- LoreNode class for interacting with public-inbox servers.
- Thread fetching by message ID and by search query.
- Single message fetching by message ID.
- Batch operations with rate limiting.
- Strict thread filtering and sort-by-received support.
- Configurable User-Agent and external session injection.
- Server validation via HEAD request to help endpoint.
