importfeed: wrong "from" annotation when feed has no title (regression in 10.20260901)
- Dominant language
- Python
- Stars
- 29
- Forks
- 5
- PR merge metrics
- No merged PRs in 30d
Description
## `importfeed`: wrong "from" annotation when feed has no title (regression in 10.20260901)
When `git annex importfeed` processes a feed whose `` element is empty,
the per-item progress note says `from ""` (two bare double-quotes) instead of
`from <feed-url>`. The fallback to the feed URL is never reached.
**Introduced by**: [10.20260717-5-g3928fac7fd AKA 10.20260901~76](https://github.com/con/git-annex/commit/3928fac7fd52e667851a2b22f47073017a81fda8)
### Impact
Users running `importfeed` on feeds with no `<title>` see uninformative
`from ""` annotations instead of the feed URL during downloads. The previous
behaviour (showing the feed URL as the description) is also not reached for
any feed regardless of title content, because the null-check always passes.
### Root cause
`Command/ImportFeed.hs` lines ~175-180:
```haskell
let feedtitle = '"' : decodeBS (fromFeedText $ getFeedTitle f) ++ "\""
unless (null feedtitle) $
showNote (UnquotedString feedtitle)
let feeddesc = if null feedtitle
then url
else feedtitle
```
`feedtitle` is constructed as `'"' : <rest>` so `null feedtitle` is always
`False` — both `unless` and the `if` are dead code. When the feed title is
empty, `feedtitle` becomes the two-character string `""` and that value is used
as `feeddesc`, producing `from ""` in the download note.
The fix is to check whether the inner string (before wrapping in quotes) is
empty, e.g.:
```haskell
let rawtitle = decodeBS (fromFeedText $ getFeedTitle f)
let feedtitle = '"' : rawtitle ++ "\""
unless (null rawtitle) $
showNote (UnquotedString feedtitle)
let feeddesc = if null rawtitle
then url
else feedtitle
```
<details><summary>Reproducer (POSIX shell, exits 0 when bug fires)</summary>
```sh
#!/bin/sh
# Reproducer: importfeed feedtitle null-check is unreachable — wrong feeddesc
# when feed has no title (git-annex 10.20260901 regression).
#
# Bug location: Command/ImportFeed.hs ~lines 175-180 (commit 3928fac7fd)
#
# let feedtitle = '"' : decodeBS (fromFeedText $ getFeedTitle f) ++ "\""
# unless (null feedtitle) $ -- NEVER fires: list starts with '"'
# showNote (UnquotedString feedtitle)
# let feeddesc = if null feedtitle -- ALWAYS false for the same reason
# then url
# else feedtitle -- feedtitle == "\"\"" when title is empty
#
# Result: per-item download note says '(from "")' instead of '(from <url>)'.
# The feed-level note also shows '("")' instead of nothing / the URL.
#
# Verified against:
# 10.20260901 : bug fires — 'from ""' in output
# 10.20260421 : no bug — feature not yet present, no 'from' note at all
#
# Exit convention: exits 0 when the bug is confirmed, non-zero otherwise.
set -eux
PS4='> '
WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/ga-importfeed-titlebug-XXXXXXX")"
cd "$WORKDIR"
# --------------------------------------------------------------------------
# 0. git-annex binary (override with GA_BIN env var to point at 10.20260901)
# --------------------------------------------------------------------------
GA="${GA_BIN:-git-annex}"
"$GA" version
# --------------------------------------------------------------------------
# 1. Initialise a throw-away annex repo
# --------------------------------------------------------------------------
git init
"$GA" init
git config annex.security.allowed-ip-addresses "127.0.0.1"
# --------------------------------------------------------------------------
# 2. Write feed and enclosure files
# --------------------------------------------------------------------------
cat > feed.rss <<'FEED'
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>
http://localhost:18991/
Feed with empty title — reproducer for 10.20260901 bug
Test Item
FEED
# Minimal non-empty file to serve as the enclosure
printf 'ID3\000\000\000\000\000\000\000hello' > test.mp3
# --------------------------------------------------------------------------
# 3. Start a minimal HTTP server on localhost:18991
# --------------------------------------------------------------------------
READY_FILE="$WORKDIR/.srv_ready"
python3 -c "
import http.server, os, sys
WORKDIR = sys.argv[1]
READY = sys.argv[2]
class Handler(http.server.BaseHTTPRequestHandler):
def log_message(self, *a): pass
def do_GET(self):
name = self.path.lstrip('/')
fpath = os.path.join(WORKDIR, name)
if os.path.isfile(fpath):
self.send_response(200)
if name.endswith('.rss'):
ct = 'application/rss+xml'
else:
ct = 'application/octet-stream'
self.send_header('Content-Type', ct)
self.end_headers()
with open(fpath, 'rb') as f:
self.wfile.write(f.read())
else:
self.send_response(404)
self.end_headers()
srv = http.server.HTTPServer(('127.0.0.1', 18991), Handler)
open(READY, 'w').close()
srv.serve_forever()
" "$WORKDIR" "$READY_FILE" &
HTTP_PID=$!
trap 'kill "$HTTP_PID" 2>/dev/null; exit' EXIT INT TERM
# Wait for the server (up to 5 s)
i=0
while [ ! -f "$READY_FILE" ]; do
i=$((i + 1))
if [ "$i" -ge 50 ]; then
echo "ERROR: HTTP server did not start within 5 s" >&2
exit 2
fi
sleep 0.1
done
# --------------------------------------------------------------------------
# 4. Run importfeed and capture output
# --------------------------------------------------------------------------
FEED_URL="http://localhost:18991/feed.rss"
"$GA" importfeed "$FEED_URL" > output.txt 2>&1 || true
echo "--- importfeed output ---"
cat output.txt
echo "--- end output ---"
# --------------------------------------------------------------------------
# 5. Assert the bug
# --------------------------------------------------------------------------
WRONG='from ""'
CORRECT="from \"${FEED_URL}\""
if grep -qF "$WRONG" output.txt; then
echo "BUG CONFIRMED: output contains '${WRONG}' — feeddesc is wrong (empty quotes)"
else
echo "WRONG_STRING_NOT_FOUND: '${WRONG}' absent — bug may be fixed or output format changed"
exit 1
fi
if grep -qF "$CORRECT" output.txt; then
echo "UNEXPECTED: correct string present — bug not triggered"
exit 1
fi
echo "RESULT: bug confirmed — 'from \"\"' shown instead of feed URL"
exit 0
```
Notes:
- Requires `annex.security.allowed-ip-addresses "127.0.0.1"` — git-annex refuses localhost by default.
- Verified to exit 0 on 10.20260901 and exit 1 on 10.20260421.
- The enclosure URL must be on the same server (localhost:18991).
Regression evidence
The introducing commit is [10.20260717-5-g3928fac7fd AKA 10.20260901~76](https://github.com/con/git-annex/commit/3928fac7fd52e667851a2b22f47073017a81fda8).
The parent commit (`035ac84fe1`) used the old code where the empty-title case
was handled by the pattern match `"" -> noop`. The reproducer exits 1 on
10.20260421 (no "from" note at all, feature not yet present) and exits 0 on
10.20260901 (bug fires). The commit message and CHANGELOG entry describe the
`feeddescription` field as a correct improvement, with no mention of the
empty-title edge case being broken — the intent was to show the feed name, not
to break the empty-title fallback.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in Command/ImportFeed.hs around lines 175–180 and run the provided POSIX reproducer against the current git-annex binary. Check the empty-feed-title path and verify that the feed-level and per-item notes use the feed URL rather than empty quotes, while titled feeds retain their title annotation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- haskell, python, shell
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100