Skip to content

deb: split Debian revision at the last hyphen - #1646

Open
twaik wants to merge 1 commit into
aptly-dev:masterfrom
twaik:fix-version-split-last-hyphen
Open

twaik wants to merge 1 commit into
aptly-dev:masterfrom
twaik:fix-version-split-last-hyphen

Conversation

@twaik

@twaik twaik commented Oct 11, 2026

Copy link
Copy Markdown

Description of the Change

Per Debian Policy, the version is split at the last hyphen: https://www.debian.org/doc/debian-policy/ch-controlfields.html#version

Add tests for the ordering of '+', which is a regular non-letter sorted by its ASCII value (see Comparing version numbers on the same page).
Why this change is important?

Checklist

  • allow Maintainers to edit PR
  • unit-test added
  • functional test added/updated (not applicable)
  • man page updated (not applicable)
  • bash completion updated (not applicable)
  • documentation updated (not applicable)
  • author name in AUTHORS

Per Debian Policy, the version is split at the last hyphen:
https://www.debian.org/doc/debian-policy/ch-controlfields.html#version

Add tests for the ordering of '+', which is a regular non-letter sorted by its ASCII value (see Comparing version numbers on the same page).
@twaik

twaik commented Oct 11, 2026

Copy link
Copy Markdown
Author

This PR doesn't close any open issue. I opened it because we can't upload libwebp 1.6.0-rc1+really1.6.0-0 to our repository on top of the existing 1.6.0-rc1-0, even though dpkg and apt both treat it as newer:

$ dpkg --compare-versions '1.6.0-rc1+really1.6.0-0' gt '1.6.0-rc1-0' && echo newer
newer

aptly's CompareVersions disagrees with them for this pair.

@codecov

codecov Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.06%. Comparing base (f59b0d2) to head (97cf022).
⚠️ Report is 6 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1646      +/-   ##
==========================================
- Coverage   77.37%   77.06%   -0.32%     
==========================================
  Files         165      165              
  Lines       15747    15747              
==========================================
- Hits        12185    12136      -49     
- Misses       2356     2409      +53     
+ Partials     1206     1202       -4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@twaik

twaik commented Oct 11, 2026 •

Copy link
Copy Markdown
Author

Probably someone should switch debian bullseye container sources to debian archive to avoid all these 404 errors.

Comment thread deb/version.go
}

i = strings.Index(ver, "-")
i = strings.LastIndex(ver, "-")

@thunder-coding thunder-coding Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You sure? From lib/dpkg/parsehelp.c (https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/parsehelp.c):

	colon = strchr(string, ':');
	if (colon) {
		long epoch;

		errno = 0;
		epoch = strtol(string, &eepochcolon, 10);
		if (string == eepochcolon)
			return dpkg_put_error(err, _("epoch in version is empty"));
		if (colon != eepochcolon)
			return dpkg_put_error(err, _("epoch in version is not number"));
		if (epoch < 0)
			return dpkg_put_error(err, _("epoch in version is negative"));
		if (epoch > INT_MAX || errno == ERANGE)
			return dpkg_put_error(err, _("epoch in version is too big"));
		if (!*++colon)
			return dpkg_put_error(err, _("nothing after colon in version number"));
		string = colon;
		rversion->epoch = epoch;
	} else {
		rversion->epoch = 0;
	}

	rversion->version = nfstrnsave(string, end - string);


  // [SEETHIS] this is the part where the revision is extracted
	hyphen = strrchr(rversion->version, '-');
	if (hyphen) {
		*hyphen++ = '\0';

		if (*hyphen == '\0')
			return dpkg_put_error(err, _("revision number is empty"));
	}
	rversion->revision = hyphen ? hyphen : "";

Look at the comment which I added with [SEETHIS] in dpkg's source, it seems to actually split at the first hyphen (unless I am missing something else).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking again slightly deeper. Yeah the docs to mention that it should be splitted at last hyphen, but dpkg is doing something different internally.

The incompatibility I believe exists in dpkg's lib/dpkg/version.c:

static int
verrevcmp(const char *a, const char *b)
{
	if (a == NULL)
		a = "";
	if (b == NULL)
		b = "";

	while (*a || *b) {
		int first_diff = 0;

		while ((*a && !c_isdigit(*a)) || (*b && !c_isdigit(*b))) {
			int ac = order(*a);
			int bc = order(*b);

			if (ac != bc)
				return ac - bc;

			a++;
			b++;
		}
		while (*a == '0')
			a++;
		while (*b == '0')
			b++;
		while (c_isdigit(*a) && c_isdigit(*b)) {
			if (!first_diff)
				first_diff = *a - *b;
			a++;
			b++;
		}

		if (c_isdigit(*a))
			return 1;
		if (c_isdigit(*b))
			return -1;
		if (first_diff)
			return first_diff;
	}

	return 0;
}

and aptly's CompareVersionPart deb/version.go:

func compareVersionPart(part1, part2 string) int {
	i1, i2 := 0, 0
	l1, l2 := len(part1), len(part2)

	for {
		j1, j2 := i1, i2
		for j1 < l1 && !unicode.IsDigit(rune(part1[j1])) {
			j1++
		}

		for j2 < l2 && !unicode.IsDigit(rune(part2[j2])) {
			j2++
		}

		s1, s2 := part1[i1:j1], part2[i2:j2]
		r := compareLexicographic(s1, s2)
		if r != 0 {
			return r
		}

		i1, i2 = j1, j2

		for j1 < l1 && unicode.IsDigit(rune(part1[j1])) {
			j1++
		}

		for j2 < l2 && unicode.IsDigit(rune(part2[j2])) {
			j2++
		}

		s1, s2 = part1[i1:j1], part2[i2:j2]
		n1, _ := strconv.Atoi(s1)
		n2, _ := strconv.Atoi(s2)

		if n1 < n2 {
			return -1
		}
		if n1 > n2 {
			return 1
		}

		i1, i2 = j1, j2

		if i1 == l1 && i2 == l2 {
			break
		}
	}
	return 0
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// [SEETHIS] this is the part where the revision is extracted
hyphen = strrchr(rversion->version, '-');

strrchr looks for the last occurence unlike strchr which looks for the first one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay, makes sense then (somehow I didn't read it properly). But the test case I added back in 836d9f3 for 4.3.5a and 4.3.5rc1-1 needs to be left as is.

$ dpkg --compare-versions 4.3.5a lt 4.3.5-rc1-1; echo $?
0

So for CompareVersions() it should return 1 (-1 for left side is greater, 0 for equal, 1 for right side)

Yeah I was wrong in that commit for how the hyphen is supposed to split, but I still believe that CompareVersionPart() behaviour differs from that of dpkg's behaviour looking at how this breaks

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit unsure about the sign here, so correct me if I'm wrong
dpkg --compare-versions 4.3.5a lt 4.3.5-rc3-1 is true, so I'd expect -1 (the old 1 passed only because of the first-hyphen split).

For the sign convention: CompareVersions("1.0", "2.0") returns -1 in aptly, and dpkg --compare-versions 1.0 lt 2.0 is true, so -1 means "first is lower" in both.

I also ran every pair from TestCompareVersions through dpkg --compare-versions manually, and with this PR all of them match dpkg (before it, three didn't: 4.3.5a vs 4.3.5-rc3-1, 1.0+a-1 vs 1.0-a-1, and 1.6.0-rc1+really1.6.0-0 vs 1.6.0-rc1-0).

@neolynx neolynx self-assigned this Oct 12, 2026
@neolynx neolynx added the needs review Ready for review & merge label Oct 12, 2026
@neolynx
neolynx requested a review from a team October 12, 2026 14:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs review Ready for review & merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants