Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
f3daf26
Add Deb822 sources support (issue #149)
aybanda May 5, 2025
f71855c
fix: Improve Deb822 source implementation
aybanda May 5, 2025
e4c9451
Another approach and shifting files appropriately after review
aybanda May 5, 2025
82181b5
add rsource_deb822.cc into POTFILES.in
aybanda May 6, 2025
9184ec7
Add Deb822 sources support and remove handleFailedInstallation function
aybanda May 7, 2025
0a23800
Add support for additional Deb822 fields and update integration tests…
aybanda May 9, 2025
2f44455
Fix UTF-8 encoding issues in Deb822 source handling. Implement proper…
aybanda May 9, 2025
4e0fb39
Add rpackagemanager files to Makefile.am
aybanda May 15, 2025
8937715
Add Deb822 source format support header
aybanda May 19, 2025
609320f
Implement Deb822 source format handling
aybanda May 19, 2025
afe0743
Fix package manager and lister for Deb822 support
aybanda May 19, 2025
63b3c5e
Fix compilation issues
aybanda May 20, 2025
94cdb17
Fix: use MultiArchSupported()
aybanda May 20, 2025
fc0042c
Fix: Use SourcesList::SourceRecord for Deb822 conversion functions to…
aybanda May 20, 2025
77dce04
Fix: Add missing source conversion
aybanda May 20, 2025
e5e76db
Fix: Replace non-existent APT::String UTF-8 conversion with standard …
aybanda May 20, 2025
100e52e
Fix: Remove function redefinitions, add missing signedBy member
aybanda May 20, 2025
100d25a
Fix: Remove function redefinitions, replace APT::String::Strip with t…
aybanda May 20, 2025
e8dbdf5
Add dialog before auto-conversion 2) Fix reading from /etc/apt/source…
aybanda May 21, 2025
be78612
Update source record types and string conversion
aybanda May 21, 2025
ea78b57
simplify file handling
aybanda May 22, 2025
c424340
Fix Sections vs Components conversion
aybanda May 22, 2025
d879ae4
Fix SourcesList vs pkgSourceList namespace usage
aybanda May 22, 2025
1c65681
Fix error handling
aybanda May 22, 2025
bc24d2b
Fix build errors
aybanda May 22, 2025
d0184bc
Add debug prints in RGRepositoryEditor::Run to trace source loading
aybanda Jun 1, 2025
45f4f68
Add second debug print in RGRepositoryEditor
aybanda Jun 1, 2025
ecb8bb1
Add debug print after gtk_list_store_set in RGRepositoryEditor::Run
aybanda Jun 1, 2025
942e96d
Read Deb822 .sources files from sourceparts directory to match APT be…
aybanda Jun 20, 2025
6e2c4b7
Preserve extra Deb822 fields and disable auto-conversion of legacy so…
aybanda Jun 20, 2025
1e6168e
fix: always write sources.list in classic format, only .sources use D…
aybanda Jul 8, 2025
0828984
Fix: trailing spaces, empty lines, URL modification, section reorderi…
aybanda Jul 11, 2025
8d568b5
preserve section order, trim extra lines, handle Deb822 enable/disabl…
aybanda Jul 13, 2025
01f2ae9
fix: preserve Deb822 flag when editing sources in repository window t…
aybanda Jul 19, 2025
6f0643e
fix: always scan /etc/apt/sources.list.d/ for Deb822 .sources files, …
aybanda Jul 19, 2025
b679b09
fix: use std::cout for debug output in ReadSources instead of g_print…
aybanda Jul 19, 2025
d86e93d
fix: ensure /etc/apt/sources.list.d/ is always scanned for Deb822 sou…
aybanda Jul 19, 2025
2372c57
debug: add detailed debug prints for Deb822 .sources file discovery a…
aybanda Jul 19, 2025
0eb0093
debug: add detailed debug prints to Deb822 parser to trace stanza and…
aybanda Jul 19, 2025
0683195
debug: add detailed debug prints to Deb822 parser in rsource_deb822.c…
aybanda Jul 19, 2025
bc23446
test: add global print to confirm rsource_deb822.cc is being used at …
aybanda Jul 19, 2025
c439acc
cleanup: remove debug prints and temporary debug code from Deb822 par…
aybanda Jul 19, 2025
cbd4b01
fix: Deb822 round-trip, enabled/disabled state, and field preservatio…
aybanda Jul 19, 2025
801c032
Merge master into feature/deb822-sources (resolve 16 conflicts)
tzh476 Sep 1, 2026
87a1a1f
fix: a whitespace-only line separates deb822 stanzas
tzh476 Sep 1, 2026
b297d32
tests: cover the deb822 stanza-separator cases
tzh476 Sep 1, 2026
cae3afa
fix: preserve Architectures, Languages and Targets when saving .sources
tzh476 Sep 1, 2026
0f287fa
gtk: drop the DEBUG prints, and cover the dialog population path
tzh476 Sep 1, 2026
517b93a
fix: preserve comments when saving .sources
tzh476 Sep 1, 2026
4c22902
fix: read the sources directory once, so repositories stop appearing …
tzh476 Sep 1, 2026
191615c
fix: editing a "Types: deb deb-src" source no longer drops deb-src
tzh476 Sep 1, 2026
2b66ca6
refactor: move the type label onto SourceRecord so the test can link it
tzh476 Sep 1, 2026
b517c57
Remove six files that are not built and have no callers
tzh476 Sep 1, 2026
27eb625
tests: drop the never-built gtest integration file
tzh476 Sep 1, 2026
3c5f0d8
tests: honour TMPDIR, and check mkdtemp before dereferencing it
tzh476 Sep 1, 2026
74eeb70
lint: strip trailing whitespace introduced by this branch
tzh476 Sep 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ common_sources = files(
'rpmindexcopy.cc',
'raptoptions.cc',
'rsources.cc',
'rsource_deb822.cc',
'rcacheactor.cc',
'rpackagelistactor.cc',
'rtagcollbuilder.cc',
Expand Down
6 changes: 1 addition & 5 deletions common/rpackagelister.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2211,11 +2211,7 @@ bool RPackageLister::xapianSearch(string searchString)

bool RPackageLister::isMultiarchSystem()
{
#ifdef WITH_APT_MULTIARCH_SUPPORT
return (APT::Configuration::getArchitectures().size() > 1);
#else
return false;
#endif
return _system->MultiArchSupported();
}

// vim:ts=3:sw=3:et
357 changes: 357 additions & 0 deletions common/rsource_deb822.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,357 @@
/* rsource_deb822.cc - Deb822 format sources support
*
* Copyright (c) 2025 Synaptic development team
*/

#include "rsource_deb822.h"
#include <apt-pkg/configuration.h>
#include <apt-pkg/fileutl.h>
#include <apt-pkg/strutl.h>
#include <fstream>
#include <sstream>
#include <locale>
#include <codecvt>
#include "i18n.h"
#include <apt-pkg/error.h>
#include <algorithm>

bool RDeb822Source::ParseDeb822File(const std::string& path, std::vector<Deb822Entry>& entries) {
std::ifstream file(path);
if (!file.is_open()) {
return false;
}
std::string line;
std::map<std::string, std::string> fields;
// Comment lines seen since the last stanza ended. deb822 has no comment
// syntax of its own, so apt treats '#' lines as belonging to the stanza
// that follows them -- which is also where WriteDeb822File emits them.
std::string pendingComment;
int stanza_count = 0;
while (std::getline(file, line)) {
// A whitespace-only line separates stanzas just like an empty one
// (deb822 / RFC 822). Testing line.empty() alone made " " count as
// content, so the following stanza's fields overwrote this one's via
// fields[key] = value and the earlier stanza was silently dropped.
// Also handles CRLF files, where the separator line is "\r".
if (line.find_first_not_of(" \t\r") == std::string::npos) {
if (!fields.empty()) {
Deb822Entry entry;
// Check required fields
if (fields.find("Types") == fields.end() || fields.find("URIs") == fields.end() || fields.find("Suites") == fields.end()) {
fields.clear();
pendingComment.clear();
continue;
}
entry.Types = fields["Types"];
entry.URIs = fields["URIs"];
entry.Suites = fields["Suites"];
entry.Components = fields.count("Components") ? fields["Components"] : "";
entry.SignedBy = fields.count("Signed-By") ? fields["Signed-By"] : "";
entry.Architectures = fields.count("Architectures") ? fields["Architectures"] : "";
entry.Languages = fields.count("Languages") ? fields["Languages"] : "";
entry.Targets = fields.count("Targets") ? fields["Targets"] : "";
// Handle Enabled/Disabled fields
if (fields.count("Enabled")) {
std::string enabled_val = fields["Enabled"];
std::transform(enabled_val.begin(), enabled_val.end(), enabled_val.begin(), ::tolower);
entry.Enabled = (enabled_val == "yes" || enabled_val == "true" || enabled_val == "1");
} else if (fields.count("Disabled")) {
std::string disabled_val = fields["Disabled"];
std::transform(disabled_val.begin(), disabled_val.end(), disabled_val.begin(), ::tolower);
entry.Enabled = !(disabled_val == "yes" || disabled_val == "true" || disabled_val == "1");
} else {
entry.Enabled = true; // Default to enabled
}
entry.Comment = pendingComment;
pendingComment.clear();
entries.push_back(entry);
stanza_count++;
fields.clear();
}
continue;
}
if (line[0] == '#') {
pendingComment += line;
pendingComment += '\n';
continue;
}
size_t colon = line.find(':');
if (colon == std::string::npos) {
continue;
}
std::string key = line.substr(0, colon);
std::string value = line.substr(colon + 1);
// Trim whitespace
key.erase(0, key.find_first_not_of(" \t"));
key.erase(key.find_last_not_of(" \t") + 1);
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t") + 1);
fields[key] = value;
}
// Handle last stanza if file does not end with blank line
if (!fields.empty()) {
Deb822Entry entry;
if (fields.find("Types") == fields.end() || fields.find("URIs") == fields.end() || fields.find("Suites") == fields.end()) {
// No debug print, just skip
} else {
entry.Types = fields["Types"];
entry.URIs = fields["URIs"];
entry.Suites = fields["Suites"];
entry.Components = fields.count("Components") ? fields["Components"] : "";
entry.SignedBy = fields.count("Signed-By") ? fields["Signed-By"] : "";
entry.Architectures = fields.count("Architectures") ? fields["Architectures"] : "";
entry.Languages = fields.count("Languages") ? fields["Languages"] : "";
entry.Targets = fields.count("Targets") ? fields["Targets"] : "";
// Handle Enabled/Disabled fields
if (fields.count("Enabled")) {
std::string enabled_val = fields["Enabled"];
std::transform(enabled_val.begin(), enabled_val.end(), enabled_val.begin(), ::tolower);
entry.Enabled = (enabled_val == "yes" || enabled_val == "true" || enabled_val == "1");
} else if (fields.count("Disabled")) {
std::string disabled_val = fields["Disabled"];
std::transform(disabled_val.begin(), disabled_val.end(), disabled_val.begin(), ::tolower);
entry.Enabled = !(disabled_val == "yes" || disabled_val == "true" || disabled_val == "1");
} else {
entry.Enabled = true; // Default to enabled
}
entry.Comment = pendingComment;
pendingComment.clear();
entries.push_back(entry);
stanza_count++;
}
}
return true;
}

bool RDeb822Source::WriteDeb822File(const std::string& path, const std::vector<Deb822Entry>& entries) {
std::ofstream file(path);
if (!file) {
return _error->Error(_("Cannot write to %s"), path.c_str());
}

for (size_t i = 0; i < entries.size(); ++i) {
const auto& entry = entries[i];

// Write preserved comments before stanza
if (!entry.Comment.empty()) {
file << entry.Comment;
if (entry.Comment.back() != '\n') file << "\n";
}

// Write Enabled field
if (entry.Enabled) {
file << "Enabled: yes" << std::endl;
} else {
file << "Enabled: no" << std::endl;
}

file << "Types: " << entry.Types << std::endl;
file << "URIs: " << entry.URIs << std::endl;
file << "Suites: " << entry.Suites << std::endl;

if (!entry.Components.empty()) {
file << "Components: " << entry.Components << std::endl;
}
if (!entry.SignedBy.empty()) {
file << "Signed-By: " << entry.SignedBy << std::endl;
}
if (!entry.Architectures.empty()) {
file << "Architectures: " << entry.Architectures << std::endl;
}
if (!entry.Languages.empty()) {
file << "Languages: " << entry.Languages << std::endl;
}
if (!entry.Targets.empty()) {
file << "Targets: " << entry.Targets << std::endl;
}

// Only add empty line between entries, not after the last one
if (i < entries.size() - 1) {
file << std::endl;
}
}

return true;
}

bool RDeb822Source::ConvertToSourceRecord(const Deb822Entry& entry, SourcesList::SourceRecord& record) {
// Parse types
bool has_deb = false;
bool has_deb_src = false;
std::istringstream typeStream(entry.Types);
std::string type;
while (std::getline(typeStream, type, ' ')) {
TrimWhitespace(type);
if (type == "deb") has_deb = true;
if (type == "deb-src") has_deb_src = true;
}

record.Type = 0;
if (has_deb) record.Type |= SourcesList::Deb;
if (has_deb_src) record.Type |= SourcesList::DebSrc;
if (!entry.Enabled) record.Type |= SourcesList::Disabled;

// Parse URIs
std::istringstream uriStream(entry.URIs);
std::string uri;
while (std::getline(uriStream, uri, ' ')) {
TrimWhitespace(uri);
if (!uri.empty()) {
record.URI = uri;
break;
}
}

// Parse suites
std::istringstream suiteStream(entry.Suites);
std::string suite;
while (std::getline(suiteStream, suite, ' ')) {
TrimWhitespace(suite);
if (!suite.empty()) {
record.Dist = suite;
break;
}
}

// Parse components
std::istringstream compStream(entry.Components);
std::string comp;
std::vector<std::string> sections;
while (std::getline(compStream, comp, ' ')) {
TrimWhitespace(comp);
if (!comp.empty()) {
sections.push_back(comp);
}
}

// Set sections
if (!sections.empty()) {
record.NumSections = sections.size();
record.Sections = new std::string[record.NumSections];
for (unsigned short i = 0; i < record.NumSections; i++) {
record.Sections[i] = sections[i];
}
}

// Preserve extra fields in Comment
std::stringstream commentStream;
if (!entry.SignedBy.empty()) {
commentStream << "Signed-By: " << entry.SignedBy << std::endl;
}
if (!entry.Architectures.empty()) {
commentStream << "Architectures: " << entry.Architectures << std::endl;
}
if (!entry.Languages.empty()) {
commentStream << "Languages: " << entry.Languages << std::endl;
}
if (!entry.Targets.empty()) {
commentStream << "Targets: " << entry.Targets << std::endl;
}
record.Comment = commentStream.str();

return true;
}

bool RDeb822Source::ConvertFromSourceRecord(const SourcesList::SourceRecord& record, Deb822Entry& entry) {
// Set types
std::stringstream typeStream;
if (record.Type & SourcesList::Deb) {
typeStream << "deb ";
}
if (record.Type & SourcesList::DebSrc) {
typeStream << "deb-src ";
}
entry.Types = typeStream.str();
TrimWhitespace(entry.Types);

// Set URI
entry.URIs = record.URI;

// Set suite
entry.Suites = record.Dist;

// Set components
std::stringstream compStream;
for (unsigned short i = 0; i < record.NumSections; i++) {
compStream << record.Sections[i] << " ";
}
entry.Components = compStream.str();
TrimWhitespace(entry.Components);

// Set enabled state
entry.Enabled = !(record.Type & SourcesList::Disabled);

// Parse extra fields from Comment
if (!record.Comment.empty()) {
std::istringstream iss(record.Comment);
std::string line;
while (std::getline(iss, line)) {
size_t colon = line.find(":");
if (colon == std::string::npos) continue;
std::string key = line.substr(0, colon);
std::string value = line.substr(colon + 1);
TrimWhitespace(key);
TrimWhitespace(value);
if (key == "Signed-By") entry.SignedBy = value;
else if (key == "Architectures") entry.Architectures = value;
else if (key == "Languages") entry.Languages = value;
else if (key == "Targets") entry.Targets = value;
}
}

return true;
}

void RDeb822Source::TrimWhitespace(std::string& str) {
const std::string whitespace = " \t\r\n";
size_t start = str.find_first_not_of(whitespace);
if (start == std::string::npos) {
str.clear();
return;
}
size_t end = str.find_last_not_of(whitespace);
str = str.substr(start, end - start + 1);
}

bool RDeb822Source::ParseStanza(std::ifstream& file, std::map<std::string, std::string>& fields) {
std::string line;
bool inStanza = false;

while (std::getline(file, line)) {
// Skip empty lines
if (line.empty()) {
if (inStanza) {
return true;
}
continue;
}

// Skip comments
if (line[0] == '#') {
continue;
}

// Check for stanza start
if (line.find("Types:") != std::string::npos) {
inStanza = true;
}

if (inStanza) {
size_t colonPos = line.find(':');
if (colonPos != std::string::npos) {
std::string key = line.substr(0, colonPos);
std::string value = line.substr(colonPos + 1);

// Trim whitespace
key.erase(0, key.find_first_not_of(" \t"));
key.erase(key.find_last_not_of(" \t") + 1);
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t") + 1);

fields[key] = value;
}
}
}

return !fields.empty();
}
Loading