feat: changes screen implementation (#117)

Add support of changes generation. Changes will be generated (unless explicitly asked not to) automatically during check process (i.e. `repo-update --dry-run` and aliases) and uploaded to the remote server. Changes can be reviewed either by web interface or by special subcommands.

Changes will be automatically cleared during next successful build
This commit is contained in:
Evgenii Alekseev 2023-11-30 14:56:41 +02:00 committed by GitHub
parent acc204de6d
commit 2a9eab5f1a
81 changed files with 2107 additions and 382 deletions

View File

@ -20,6 +20,14 @@ ahriman.application.handlers.backup module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.application.handlers.change module
------------------------------------------
.. automodule:: ahriman.application.handlers.change
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.clean module ahriman.application.handlers.clean module
----------------------------------------- -----------------------------------------

View File

@ -100,6 +100,14 @@ ahriman.core.database.migrations.m011\_repository\_name module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.core.database.migrations.m012\_last\_commit\_sha module
---------------------------------------------------------------
.. automodule:: ahriman.core.database.migrations.m012_last_commit_sha
:members:
:no-undoc-members:
:show-inheritance:
Module contents Module contents
--------------- ---------------

View File

@ -20,6 +20,14 @@ ahriman.core.database.operations.build\_operations module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.core.database.operations.changes\_operations module
-----------------------------------------------------------
.. automodule:: ahriman.core.database.operations.changes_operations
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.database.operations.logs\_operations module ahriman.core.database.operations.logs\_operations module
-------------------------------------------------------- --------------------------------------------------------

View File

@ -20,6 +20,14 @@ ahriman.core.formatters.build\_printer module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.core.formatters.changes\_printer module
-----------------------------------------------
.. automodule:: ahriman.core.formatters.changes_printer
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.formatters.configuration\_paths\_printer module ahriman.core.formatters.configuration\_paths\_printer module
------------------------------------------------------------ ------------------------------------------------------------

View File

@ -20,6 +20,14 @@ ahriman.core.repository.executor module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.core.repository.package\_info module
--------------------------------------------
.. automodule:: ahriman.core.repository.package_info
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.repository.repository module ahriman.core.repository.repository module
----------------------------------------- -----------------------------------------

View File

@ -36,6 +36,14 @@ ahriman.models.build\_status module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.models.changes module
-----------------------------
.. automodule:: ahriman.models.changes
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.context\_key module ahriman.models.context\_key module
---------------------------------- ----------------------------------

View File

@ -20,6 +20,14 @@ ahriman.web.schemas.auth\_schema module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.web.schemas.changes\_schema module
------------------------------------------
.. automodule:: ahriman.web.schemas.changes_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.counters\_schema module ahriman.web.schemas.counters\_schema module
------------------------------------------- -------------------------------------------

View File

@ -4,6 +4,14 @@ ahriman.web.views.v1.status package
Submodules Submodules
---------- ----------
ahriman.web.views.v1.status.changes module
------------------------------------------
.. automodule:: ahriman.web.views.v1.status.changes
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.v1.status.logs module ahriman.web.views.v1.status.logs module
--------------------------------------- ---------------------------------------

View File

@ -114,8 +114,8 @@ But for some cases you would like to have multiple different reports with the sa
type = email type = email
... ...
How do I add new package How to add new package
^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: shell .. code-block:: shell
@ -237,6 +237,27 @@ Normally the service handles VCS packages correctly, however it requires additio
pacman -S breezy darcs mercurial subversion pacman -S breezy darcs mercurial subversion
How to review changes before build
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In this scenario, the update process must be separated to several stages. First, it is required to check updates:
.. code-block:: shell
sudo -u ahriman ahriman repo-check
During the check process, the service will generate changes from the last known commit and will send it to remote service. In order to verify source files changes, the web interface or special subcommand can be used:
.. code-block:: shell
ahriman package-changes ahriman
After validation, the operator can run update process with approved list of packages, e.g.:
.. code-block:: shell
sudo -u ahriman ahriman repo-update ahriman
How to remove package How to remove package
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^

View File

@ -2,6 +2,6 @@
Description=ArcH linux ReposItory MANager (%i) Description=ArcH linux ReposItory MANager (%i)
[Service] [Service]
ExecStart=/usr/bin/ahriman --repository-id "%I" repo-update --refresh ExecStart=/usr/bin/ahriman --repository-id "%I" repo-update --no-changes --refresh
User=ahriman User=ahriman
Group=ahriman Group=ahriman

View File

@ -36,13 +36,27 @@
<hr class="col-12"> <hr class="col-12">
<h3>Environment variables</h3> <div id="package-info-variables-block" hidden>
<div id="package-info-variables-div" class="form-group row"></div> <h3>Environment variables</h3>
<div id="package-info-variables-div" class="form-group row"></div>
<hr class="col-12"> <hr class="col-12">
</div>
<h3>Build logs</h3> <nav>
<pre class="language-logs"><samp id="package-info-logs-input" class="pre-scrollable language-logs"></samp><button id="package-info-logs-copy-button" type="button" class="btn language-logs" onclick="copyLogs()"><i class="bi bi-clipboard"></i> copy</button></pre> <div class="nav nav-tabs" role="tablist">
<button id="package-info-logs-button" class="nav-link active" data-bs-toggle="tab" data-bs-target="#package-info-logs" type="button" role="tab" aria-controls="package-info-logs" aria-selected="true"><h3>Build logs</h3></button>
<button id="package-info-changes-button" class="nav-link" data-bs-toggle="tab" data-bs-target="#package-info-changes" type="button" role="tab" aria-controls="package-info-changes" aria-selected="false"><h3>Changes</h3></button>
</div>
</nav>
<div class="tab-content" id="nav-tabContent">
<div id="package-info-logs" class="tab-pane fade show active" role="tabpanel" aria-labelledby="package-info-logs-button" tabindex="0">
<pre class="language-console"><code id="package-info-logs-input" class="pre-scrollable language-console"></code><button id="package-info-logs-copy-button" type="button" class="btn language-console" onclick="copyLogs()"><i class="bi bi-clipboard"></i> copy</button></pre>
</div>
<div id="package-info-changes" class="tab-pane fade" role="tabpanel" aria-labelledby="package-info-changes-button" tabindex="0">
<pre class="language-diff"><code id="package-info-changes-input" class="pre-scrollable language-diff"></code><button id="package-info-changes-copy-button" type="button" class="btn language-diff" onclick="copyChanges()"><i class="bi bi-clipboard"></i> copy</button></pre>
</div>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button id="package-info-update-button" type="submit" class="btn btn-success" onclick="packageInfoUpdate()" data-bs-dismiss="modal" hidden><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button> <button id="package-info-update-button" type="submit" class="btn btn-success" onclick="packageInfoUpdate()" data-bs-dismiss="modal" hidden><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button>
@ -68,9 +82,11 @@
packageInfoUpstreamUrl.empty(); packageInfoUpstreamUrl.empty();
packageInfoVersion.empty(); packageInfoVersion.empty();
packageInfoVariablesBlock.attr("hidden", true);
packageInfoVariablesDiv.empty(); packageInfoVariablesDiv.empty();
packageInfoLogsInput.empty(); packageInfoLogsInput.empty();
packageInfoChangesInput.empty();
packageInfoModal.trigger("reset"); packageInfoModal.trigger("reset");
@ -80,6 +96,9 @@
const packageInfoLogsInput = $("#package-info-logs-input"); const packageInfoLogsInput = $("#package-info-logs-input");
const packageInfoLogsCopyButton = $("#package-info-logs-copy-button"); const packageInfoLogsCopyButton = $("#package-info-logs-copy-button");
const packageInfoChangesInput = $("#package-info-changes-input");
const packageInfoChangesCopyButton = $("#package-info-changes-copy-button");
const packageInfoAurUrl = $("#package-info-aur-url"); const packageInfoAurUrl = $("#package-info-aur-url");
const packageInfoDepends = $("#package-info-depends"); const packageInfoDepends = $("#package-info-depends");
const packageInfoGroups = $("#package-info-groups"); const packageInfoGroups = $("#package-info-groups");
@ -89,8 +108,14 @@
const packageInfoUpstreamUrl = $("#package-info-upstream-url"); const packageInfoUpstreamUrl = $("#package-info-upstream-url");
const packageInfoVersion = $("#package-info-version"); const packageInfoVersion = $("#package-info-version");
const packageInfoVariablesBlock = $("#package-info-variables-block");
const packageInfoVariablesDiv = $("#package-info-variables-div"); const packageInfoVariablesDiv = $("#package-info-variables-div");
async function copyChanges() {
const changes = packageInfoChangesInput.text();
await copyToClipboard(changes, packageInfoChangesCopyButton);
}
async function copyLogs() { async function copyLogs() {
const logs = packageInfoLogsInput.text(); const logs = packageInfoLogsInput.text();
await copyToClipboard(logs, packageInfoLogsCopyButton); await copyToClipboard(logs, packageInfoLogsCopyButton);
@ -142,6 +167,24 @@
packageInfoVariablesDiv.append(variableInput); packageInfoVariablesDiv.append(variableInput);
} }
function loadChanges(packageBase, onFailure) {
$.ajax({
url: `/api/v1/packages/${packageBase}/changes`,
data: {
architecture: repository.architecture,
repository: repository.repository,
},
type: "GET",
dataType: "json",
success: response => {
const changes = response.changes;
packageInfoChangesInput.text(changes || "");
packageInfoChangesInput.map((_, el) => hljs.highlightElement(el));
},
error: onFailure,
});
}
function loadLogs(packageBase, onFailure) { function loadLogs(packageBase, onFailure) {
$.ajax({ $.ajax({
url: `/api/v2/packages/${packageBase}/logs`, url: `/api/v2/packages/${packageBase}/logs`,
@ -156,6 +199,7 @@
return `[${new Date(1000 * log_record.created).toISOString()}] ${log_record.message}`; return `[${new Date(1000 * log_record.created).toISOString()}] ${log_record.message}`;
}); });
packageInfoLogsInput.text(logs.join("\n")); packageInfoLogsInput.text(logs.join("\n"));
packageInfoLogsInput.map((_, el) => hljs.highlightElement(el));
}, },
error: onFailure, error: onFailure,
}); });
@ -228,6 +272,7 @@
success: response => { success: response => {
packageInfoVariablesDiv.empty(); packageInfoVariablesDiv.empty();
response.map(patch => insertVariable(packageBase, patch)); response.map(patch => insertVariable(packageBase, patch));
packageInfoVariablesBlock.attr("hidden", response.length === 0);
}, },
error: onFailure, error: onFailure,
}); });
@ -260,6 +305,7 @@
loadPackage(packageBase, onFailure); loadPackage(packageBase, onFailure);
loadPatches(packageBase, onFailure); loadPatches(packageBase, onFailure);
loadLogs(packageBase, onFailure); loadLogs(packageBase, onFailure);
loadChanges(packageBase, onFailure)
if (isPackageBaseSet) packageInfoModal.modal("show"); if (isPackageBaseSet) packageInfoModal.modal("show");
} }

View File

@ -15,6 +15,8 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/extensions/resizable/bootstrap-table-resizable.js" integrity="sha384-wd8Vc6Febikdnsnk9vthRWRvMwffw246vhqiqNO3aSNe1maTEA07Vh3zAQiSyDji" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/extensions/resizable/bootstrap-table-resizable.js" integrity="sha384-wd8Vc6Febikdnsnk9vthRWRvMwffw246vhqiqNO3aSNe1maTEA07Vh3zAQiSyDji" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/extensions/filter-control/bootstrap-table-filter-control.js" integrity="sha384-NIqcjpr/3eZI1iNzz7hgT5rgp70qFUzkZffeCgVva9gi80B5vqcm7gn+8QvlWxko" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.22.1/dist/extensions/filter-control/bootstrap-table-filter-control.js" integrity="sha384-NIqcjpr/3eZI1iNzz7hgT5rgp70qFUzkZffeCgVva9gi80B5vqcm7gn+8QvlWxko" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js" integrity="sha384-F/bZzf7p3Joyp5psL90p/p89AZJsndkSoGwRpXcZhleCWhd8SnRuoYo4d0yirjJp" crossorigin="anonymous" type="application/javascript"></script>
<script> <script>
async function copyToClipboard(text, button) { async function copyToClipboard(text, button) {
if (navigator.clipboard === undefined) { if (navigator.clipboard === undefined) {

View File

@ -11,6 +11,8 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/daterangepicker@3.1.0/daterangepicker.css" integrity="sha384-zLkQsiLfAQqGeIJeKLC+rcCR1YoYaQFLCL7cLDUoKE1ajKJzySpjzWGfYS2vjSG+" crossorigin="anonymous" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/daterangepicker@3.1.0/daterangepicker.css" integrity="sha384-zLkQsiLfAQqGeIJeKLC+rcCR1YoYaQFLCL7cLDUoKE1ajKJzySpjzWGfYS2vjSG+" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css" integrity="sha384-eFTL69TLRZTkNfYZOLM+G04821K1qZao/4QLJbet1pP4tcF+fdXq/9CdqAbWRl/L" crossorigin="anonymous" type="text/css">
<style> <style>
.pre-scrollable { .pre-scrollable {
display: block; display: block;

View File

@ -1,6 +1,6 @@
# AUTOMATICALLY GENERATED by `shtab` # AUTOMATICALLY GENERATED by `shtab`
_shtab_ahriman_subparsers=('aur-search' 'search' 'help-commands-unsafe' 'help' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-repositories' 'service-run' 'run' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web') _shtab_ahriman_subparsers=('aur-search' 'search' 'help-commands-unsafe' 'help' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-changes' 'package-changes-remove' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-repositories' 'service-run' 'run' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web')
_shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--log-handler' '-q' '--quiet' '--report' '--no-report' '-r' '--repository' '--unsafe' '-V' '--version' '--wait-timeout') _shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--log-handler' '-q' '--quiet' '--report' '--no-report' '-r' '--repository' '--unsafe' '-V' '--version' '--wait-timeout')
_shtab_ahriman_aur_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by') _shtab_ahriman_aur_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by')
@ -13,6 +13,8 @@ _shtab_ahriman_version_option_strings=('-h' '--help')
_shtab_ahriman_package_add_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username' '-v' '--variable') _shtab_ahriman_package_add_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username' '-v' '--variable')
_shtab_ahriman_add_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username' '-v' '--variable') _shtab_ahriman_add_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username' '-v' '--variable')
_shtab_ahriman_package_update_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username' '-v' '--variable') _shtab_ahriman_package_update_option_strings=('-h' '--help' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username' '-v' '--variable')
_shtab_ahriman_package_changes_option_strings=('-h' '--help' '-e' '--exit-code')
_shtab_ahriman_package_changes_remove_option_strings=('-h' '--help')
_shtab_ahriman_package_remove_option_strings=('-h' '--help') _shtab_ahriman_package_remove_option_strings=('-h' '--help')
_shtab_ahriman_remove_option_strings=('-h' '--help') _shtab_ahriman_remove_option_strings=('-h' '--help')
_shtab_ahriman_package_status_option_strings=('-h' '--help' '--ahriman' '-e' '--exit-code' '--info' '--no-info' '-s' '--status') _shtab_ahriman_package_status_option_strings=('-h' '--help' '--ahriman' '-e' '--exit-code' '--info' '--no-info' '-s' '--status')
@ -25,12 +27,12 @@ _shtab_ahriman_patch_list_option_strings=('-h' '--help' '-e' '--exit-code' '-v'
_shtab_ahriman_patch_remove_option_strings=('-h' '--help' '-v' '--variable') _shtab_ahriman_patch_remove_option_strings=('-h' '--help' '-v' '--variable')
_shtab_ahriman_patch_set_add_option_strings=('-h' '--help' '-t' '--track') _shtab_ahriman_patch_set_add_option_strings=('-h' '--help' '-t' '--track')
_shtab_ahriman_repo_backup_option_strings=('-h' '--help') _shtab_ahriman_repo_backup_option_strings=('-h' '--help')
_shtab_ahriman_repo_check_option_strings=('-h' '--help' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_repo_check_option_strings=('-h' '--help' '--changes' '--no-changes' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_check_option_strings=('-h' '--help' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_check_option_strings=('-h' '--help' '--changes' '--no-changes' '-e' '--exit-code' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_repo_create_keyring_option_strings=('-h' '--help') _shtab_ahriman_repo_create_keyring_option_strings=('-h' '--help')
_shtab_ahriman_repo_create_mirrorlist_option_strings=('-h' '--help') _shtab_ahriman_repo_create_mirrorlist_option_strings=('-h' '--help')
_shtab_ahriman_repo_daemon_option_strings=('-h' '--help' '-i' '--interval' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_repo_daemon_option_strings=('-h' '--help' '-i' '--interval' '--aur' '--no-aur' '--changes' '--no-changes' '--dependencies' '--no-dependencies' '--dry-run' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_daemon_option_strings=('-h' '--help' '-i' '--interval' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_daemon_option_strings=('-h' '--help' '-i' '--interval' '--aur' '--no-aur' '--changes' '--no-changes' '--dependencies' '--no-dependencies' '--dry-run' '--local' '--no-local' '--manual' '--no-manual' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_repo_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '--increment' '--no-increment' '-e' '--exit-code' '-s' '--status' '-u' '--username') _shtab_ahriman_repo_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '--increment' '--no-increment' '-e' '--exit-code' '-s' '--status' '-u' '--username')
_shtab_ahriman_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '--increment' '--no-increment' '-e' '--exit-code' '-s' '--status' '-u' '--username') _shtab_ahriman_rebuild_option_strings=('-h' '--help' '--depends-on' '--dry-run' '--from-database' '--increment' '--no-increment' '-e' '--exit-code' '-s' '--status' '-u' '--username')
_shtab_ahriman_repo_remove_unknown_option_strings=('-h' '--help' '--dry-run') _shtab_ahriman_repo_remove_unknown_option_strings=('-h' '--help' '--dry-run')
@ -45,8 +47,8 @@ _shtab_ahriman_repo_sync_option_strings=('-h' '--help')
_shtab_ahriman_sync_option_strings=('-h' '--help') _shtab_ahriman_sync_option_strings=('-h' '--help')
_shtab_ahriman_repo_tree_option_strings=('-h' '--help' '-p' '--partitions') _shtab_ahriman_repo_tree_option_strings=('-h' '--help' '-p' '--partitions')
_shtab_ahriman_repo_triggers_option_strings=('-h' '--help') _shtab_ahriman_repo_triggers_option_strings=('-h' '--help')
_shtab_ahriman_repo_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--dry-run' '-e' '--exit-code' '--increment' '--no-increment' '--local' '--no-local' '--manual' '--no-manual' '-u' '--username' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_repo_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--changes' '--no-changes' '--dependencies' '--no-dependencies' '--dry-run' '-e' '--exit-code' '--increment' '--no-increment' '--local' '--no-local' '--manual' '--no-manual' '-u' '--username' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--dependencies' '--no-dependencies' '--dry-run' '-e' '--exit-code' '--increment' '--no-increment' '--local' '--no-local' '--manual' '--no-manual' '-u' '--username' '--vcs' '--no-vcs' '-y' '--refresh') _shtab_ahriman_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--changes' '--no-changes' '--dependencies' '--no-dependencies' '--dry-run' '-e' '--exit-code' '--increment' '--no-increment' '--local' '--no-local' '--manual' '--no-manual' '-u' '--username' '--vcs' '--no-vcs' '-y' '--refresh')
_shtab_ahriman_service_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman') _shtab_ahriman_service_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
_shtab_ahriman_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman') _shtab_ahriman_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
_shtab_ahriman_repo_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman') _shtab_ahriman_repo_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
@ -76,7 +78,7 @@ _shtab_ahriman_web_option_strings=('-h' '--help')
_shtab_ahriman_pos_0_choices=('aur-search' 'search' 'help-commands-unsafe' 'help' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-repositories' 'service-run' 'run' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web') _shtab_ahriman_pos_0_choices=('aur-search' 'search' 'help-commands-unsafe' 'help' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-changes' 'package-changes-remove' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-repositories' 'service-run' 'run' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web')
_shtab_ahriman___log_handler_choices=('console' 'syslog' 'journald') _shtab_ahriman___log_handler_choices=('console' 'syslog' 'journald')
_shtab_ahriman_aur_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version') _shtab_ahriman_aur_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version')
_shtab_ahriman_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version') _shtab_ahriman_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version')
@ -187,6 +189,12 @@ _shtab_ahriman_package_update__n_nargs=0
_shtab_ahriman_package_update___now_nargs=0 _shtab_ahriman_package_update___now_nargs=0
_shtab_ahriman_package_update__y_nargs=0 _shtab_ahriman_package_update__y_nargs=0
_shtab_ahriman_package_update___refresh_nargs=0 _shtab_ahriman_package_update___refresh_nargs=0
_shtab_ahriman_package_changes__h_nargs=0
_shtab_ahriman_package_changes___help_nargs=0
_shtab_ahriman_package_changes__e_nargs=0
_shtab_ahriman_package_changes___exit_code_nargs=0
_shtab_ahriman_package_changes_remove__h_nargs=0
_shtab_ahriman_package_changes_remove___help_nargs=0
_shtab_ahriman_package_remove_pos_0_nargs=+ _shtab_ahriman_package_remove_pos_0_nargs=+
_shtab_ahriman_package_remove__h_nargs=0 _shtab_ahriman_package_remove__h_nargs=0
_shtab_ahriman_package_remove___help_nargs=0 _shtab_ahriman_package_remove___help_nargs=0
@ -233,6 +241,8 @@ _shtab_ahriman_repo_backup___help_nargs=0
_shtab_ahriman_repo_check_pos_0_nargs=* _shtab_ahriman_repo_check_pos_0_nargs=*
_shtab_ahriman_repo_check__h_nargs=0 _shtab_ahriman_repo_check__h_nargs=0
_shtab_ahriman_repo_check___help_nargs=0 _shtab_ahriman_repo_check___help_nargs=0
_shtab_ahriman_repo_check___changes_nargs=0
_shtab_ahriman_repo_check___no_changes_nargs=0
_shtab_ahriman_repo_check__e_nargs=0 _shtab_ahriman_repo_check__e_nargs=0
_shtab_ahriman_repo_check___exit_code_nargs=0 _shtab_ahriman_repo_check___exit_code_nargs=0
_shtab_ahriman_repo_check___vcs_nargs=0 _shtab_ahriman_repo_check___vcs_nargs=0
@ -242,6 +252,8 @@ _shtab_ahriman_repo_check___refresh_nargs=0
_shtab_ahriman_check_pos_0_nargs=* _shtab_ahriman_check_pos_0_nargs=*
_shtab_ahriman_check__h_nargs=0 _shtab_ahriman_check__h_nargs=0
_shtab_ahriman_check___help_nargs=0 _shtab_ahriman_check___help_nargs=0
_shtab_ahriman_check___changes_nargs=0
_shtab_ahriman_check___no_changes_nargs=0
_shtab_ahriman_check__e_nargs=0 _shtab_ahriman_check__e_nargs=0
_shtab_ahriman_check___exit_code_nargs=0 _shtab_ahriman_check___exit_code_nargs=0
_shtab_ahriman_check___vcs_nargs=0 _shtab_ahriman_check___vcs_nargs=0
@ -256,8 +268,11 @@ _shtab_ahriman_repo_daemon__h_nargs=0
_shtab_ahriman_repo_daemon___help_nargs=0 _shtab_ahriman_repo_daemon___help_nargs=0
_shtab_ahriman_repo_daemon___aur_nargs=0 _shtab_ahriman_repo_daemon___aur_nargs=0
_shtab_ahriman_repo_daemon___no_aur_nargs=0 _shtab_ahriman_repo_daemon___no_aur_nargs=0
_shtab_ahriman_repo_daemon___changes_nargs=0
_shtab_ahriman_repo_daemon___no_changes_nargs=0
_shtab_ahriman_repo_daemon___dependencies_nargs=0 _shtab_ahriman_repo_daemon___dependencies_nargs=0
_shtab_ahriman_repo_daemon___no_dependencies_nargs=0 _shtab_ahriman_repo_daemon___no_dependencies_nargs=0
_shtab_ahriman_repo_daemon___dry_run_nargs=0
_shtab_ahriman_repo_daemon___local_nargs=0 _shtab_ahriman_repo_daemon___local_nargs=0
_shtab_ahriman_repo_daemon___no_local_nargs=0 _shtab_ahriman_repo_daemon___no_local_nargs=0
_shtab_ahriman_repo_daemon___manual_nargs=0 _shtab_ahriman_repo_daemon___manual_nargs=0
@ -270,8 +285,11 @@ _shtab_ahriman_daemon__h_nargs=0
_shtab_ahriman_daemon___help_nargs=0 _shtab_ahriman_daemon___help_nargs=0
_shtab_ahriman_daemon___aur_nargs=0 _shtab_ahriman_daemon___aur_nargs=0
_shtab_ahriman_daemon___no_aur_nargs=0 _shtab_ahriman_daemon___no_aur_nargs=0
_shtab_ahriman_daemon___changes_nargs=0
_shtab_ahriman_daemon___no_changes_nargs=0
_shtab_ahriman_daemon___dependencies_nargs=0 _shtab_ahriman_daemon___dependencies_nargs=0
_shtab_ahriman_daemon___no_dependencies_nargs=0 _shtab_ahriman_daemon___no_dependencies_nargs=0
_shtab_ahriman_daemon___dry_run_nargs=0
_shtab_ahriman_daemon___local_nargs=0 _shtab_ahriman_daemon___local_nargs=0
_shtab_ahriman_daemon___no_local_nargs=0 _shtab_ahriman_daemon___no_local_nargs=0
_shtab_ahriman_daemon___manual_nargs=0 _shtab_ahriman_daemon___manual_nargs=0
@ -330,6 +348,8 @@ _shtab_ahriman_repo_update__h_nargs=0
_shtab_ahriman_repo_update___help_nargs=0 _shtab_ahriman_repo_update___help_nargs=0
_shtab_ahriman_repo_update___aur_nargs=0 _shtab_ahriman_repo_update___aur_nargs=0
_shtab_ahriman_repo_update___no_aur_nargs=0 _shtab_ahriman_repo_update___no_aur_nargs=0
_shtab_ahriman_repo_update___changes_nargs=0
_shtab_ahriman_repo_update___no_changes_nargs=0
_shtab_ahriman_repo_update___dependencies_nargs=0 _shtab_ahriman_repo_update___dependencies_nargs=0
_shtab_ahriman_repo_update___no_dependencies_nargs=0 _shtab_ahriman_repo_update___no_dependencies_nargs=0
_shtab_ahriman_repo_update___dry_run_nargs=0 _shtab_ahriman_repo_update___dry_run_nargs=0
@ -350,6 +370,8 @@ _shtab_ahriman_update__h_nargs=0
_shtab_ahriman_update___help_nargs=0 _shtab_ahriman_update___help_nargs=0
_shtab_ahriman_update___aur_nargs=0 _shtab_ahriman_update___aur_nargs=0
_shtab_ahriman_update___no_aur_nargs=0 _shtab_ahriman_update___no_aur_nargs=0
_shtab_ahriman_update___changes_nargs=0
_shtab_ahriman_update___no_changes_nargs=0
_shtab_ahriman_update___dependencies_nargs=0 _shtab_ahriman_update___dependencies_nargs=0
_shtab_ahriman_update___no_dependencies_nargs=0 _shtab_ahriman_update___no_dependencies_nargs=0
_shtab_ahriman_update___dry_run_nargs=0 _shtab_ahriman_update___dry_run_nargs=0

View File

@ -1,9 +1,9 @@
.TH AHRIMAN "1" "2023\-11\-13" "ahriman" "Generated Python Manual" .TH AHRIMAN "1" "2023\-11\-29" "ahriman" "Generated Python Manual"
.SH NAME .SH NAME
ahriman ahriman
.SH SYNOPSIS .SH SYNOPSIS
.B ahriman .B ahriman
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [-V] [--wait-timeout WAIT_TIMEOUT] {aur-search,search,help-commands-unsafe,help,help-updates,help-version,version,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,patch-set-add,repo-backup,repo-check,check,repo-create-keyring,repo-create-mirrorlist,repo-daemon,daemon,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-sign,sign,repo-status-update,repo-sync,sync,repo-tree,repo-triggers,repo-update,update,service-clean,clean,repo-clean,service-config,config,repo-config,service-config-validate,config-validate,repo-config-validate,service-key-import,key-import,service-repositories,service-run,run,service-setup,init,repo-init,repo-setup,setup,service-shell,shell,service-tree-migrate,user-add,user-list,user-remove,web} ... [-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [-V] [--wait-timeout WAIT_TIMEOUT] {aur-search,search,help-commands-unsafe,help,help-updates,help-version,version,package-add,add,package-update,package-changes,package-changes-remove,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,patch-set-add,repo-backup,repo-check,check,repo-create-keyring,repo-create-mirrorlist,repo-daemon,daemon,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-sign,sign,repo-status-update,repo-sync,sync,repo-tree,repo-triggers,repo-update,update,service-clean,clean,repo-clean,service-config,config,repo-config,service-config-validate,config-validate,repo-config-validate,service-key-import,key-import,service-repositories,service-run,run,service-setup,init,repo-init,repo-setup,setup,service-shell,shell,service-tree-migrate,user-add,user-list,user-remove,web} ...
.SH DESCRIPTION .SH DESCRIPTION
ArcH linux ReposItory MANager ArcH linux ReposItory MANager
@ -74,6 +74,12 @@ application version
\fBahriman\fR \fI\,package\-add\/\fR \fBahriman\fR \fI\,package\-add\/\fR
add package add package
.TP .TP
\fBahriman\fR \fI\,package\-changes\/\fR
get package changes
.TP
\fBahriman\fR \fI\,package\-changes\-remove\/\fR
remove package changes
.TP
\fBahriman\fR \fI\,package\-remove\/\fR \fBahriman\fR \fI\,package\-remove\/\fR
remove package remove package
.TP .TP
@ -285,6 +291,29 @@ build as user
\fB\-v\fR \fI\,VARIABLE\/\fR, \fB\-\-variable\fR \fI\,VARIABLE\/\fR \fB\-v\fR \fI\,VARIABLE\/\fR, \fB\-\-variable\fR \fI\,VARIABLE\/\fR
apply specified makepkg variables to the next build apply specified makepkg variables to the next build
.SH COMMAND \fI\,'ahriman package\-changes'\/\fR
usage: ahriman package\-changes [\-h] [\-e] package
retrieve package changes stored in database
.TP
\fBpackage\fR
package base
.SH OPTIONS \fI\,'ahriman package\-changes'\/\fR
.TP
\fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty
.SH COMMAND \fI\,'ahriman package\-changes\-remove'\/\fR
usage: ahriman package\-changes\-remove [\-h] package
remove the package changes stored remotely
.TP
\fBpackage\fR
package base
.SH COMMAND \fI\,'ahriman package\-remove'\/\fR .SH COMMAND \fI\,'ahriman package\-remove'\/\fR
usage: ahriman package\-remove [\-h] package [package ...] usage: ahriman package\-remove [\-h] package [package ...]
@ -418,7 +447,7 @@ backup repository settings and database
path of the output archive path of the output archive
.SH COMMAND \fI\,'ahriman repo\-check'\/\fR .SH COMMAND \fI\,'ahriman repo\-check'\/\fR
usage: ahriman repo\-check [\-h] [\-e] [\-\-vcs | \-\-no\-vcs] [\-y] [package ...] usage: ahriman repo\-check [\-h] [\-\-changes | \-\-no\-changes] [\-e] [\-\-vcs | \-\-no\-vcs] [\-y] [package ...]
check for packages updates. Same as repo\-update \-\-dry\-run \-\-no\-manual check for packages updates. Same as repo\-update \-\-dry\-run \-\-no\-manual
@ -427,6 +456,10 @@ check for packages updates. Same as repo\-update \-\-dry\-run \-\-no\-manual
filter check by package base filter check by package base
.SH OPTIONS \fI\,'ahriman repo\-check'\/\fR .SH OPTIONS \fI\,'ahriman repo\-check'\/\fR
.TP
\fB\-\-changes\fR, \fB\-\-no\-changes\fR
calculate changes from the latest known commit if available. Only applicable in dry run mode
.TP .TP
\fB\-e\fR, \fB\-\-exit\-code\fR \fB\-e\fR, \fB\-\-exit\-code\fR
return non\-zero exit status if result is empty return non\-zero exit status if result is empty
@ -450,8 +483,9 @@ usage: ahriman repo\-create\-mirrorlist [\-h]
create package which contains list of available mirrors as set by configuration. Note, that this action will only create package, the package itself has to be built manually create package which contains list of available mirrors as set by configuration. Note, that this action will only create package, the package itself has to be built manually
.SH COMMAND \fI\,'ahriman repo\-daemon'\/\fR .SH COMMAND \fI\,'ahriman repo\-daemon'\/\fR
usage: ahriman repo\-daemon [\-h] [\-i INTERVAL] [\-\-aur | \-\-no\-aur] [\-\-dependencies | \-\-no\-dependencies] usage: ahriman repo\-daemon [\-h] [\-i INTERVAL] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes]
[\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-\-vcs | \-\-no\-vcs] [\-y] [\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run] [\-\-local | \-\-no\-local]
[\-\-manual | \-\-no\-manual] [\-\-vcs | \-\-no\-vcs] [\-y]
start process which periodically will run update process start process which periodically will run update process
@ -464,10 +498,18 @@ interval between runs in seconds
\fB\-\-aur\fR, \fB\-\-no\-aur\fR \fB\-\-aur\fR, \fB\-\-no\-aur\fR
enable or disable checking for AUR updates enable or disable checking for AUR updates
.TP
\fB\-\-changes\fR, \fB\-\-no\-changes\fR
calculate changes from the latest known commit if available. Only applicable in dry run mode
.TP .TP
\fB\-\-dependencies\fR, \fB\-\-no\-dependencies\fR \fB\-\-dependencies\fR, \fB\-\-no\-dependencies\fR
process missing package dependencies process missing package dependencies
.TP
\fB\-\-dry\-run\fR
just perform check for updates, same as check command
.TP .TP
\fB\-\-local\fR, \fB\-\-no\-local\fR \fB\-\-local\fR, \fB\-\-no\-local\fR
enable or disable checking of local packages for updates enable or disable checking of local packages for updates
@ -594,9 +636,9 @@ run triggers on empty build result as configured by settings
instead of running all triggers as set by configuration, just process specified ones in order of mention instead of running all triggers as set by configuration, just process specified ones in order of mention
.SH COMMAND \fI\,'ahriman repo\-update'\/\fR .SH COMMAND \fI\,'ahriman repo\-update'\/\fR
usage: ahriman repo\-update [\-h] [\-\-aur | \-\-no\-aur] [\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run] [\-e] usage: ahriman repo\-update [\-h] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes] [\-\-dependencies | \-\-no\-dependencies]
[\-\-increment | \-\-no\-increment] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-u USERNAME] [\-\-dry\-run] [\-e] [\-\-increment | \-\-no\-increment] [\-\-local | \-\-no\-local]
[\-\-vcs | \-\-no\-vcs] [\-y] [\-\-manual | \-\-no\-manual] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y]
[package ...] [package ...]
check for packages updates and run build process if requested check for packages updates and run build process if requested
@ -610,6 +652,10 @@ filter check by package base
\fB\-\-aur\fR, \fB\-\-no\-aur\fR \fB\-\-aur\fR, \fB\-\-no\-aur\fR
enable or disable checking for AUR updates enable or disable checking for AUR updates
.TP
\fB\-\-changes\fR, \fB\-\-no\-changes\fR
calculate changes from the latest known commit if available. Only applicable in dry run mode
.TP .TP
\fB\-\-dependencies\fR, \fB\-\-no\-dependencies\fR \fB\-\-dependencies\fR, \fB\-\-no\-dependencies\fR
process missing package dependencies process missing package dependencies

View File

@ -19,6 +19,8 @@ _shtab_ahriman_commands() {
"init:create initial service configuration, requires root" "init:create initial service configuration, requires root"
"key-import:import PGP key from public sources to the repository user" "key-import:import PGP key from public sources to the repository user"
"package-add:add existing or new package to the build queue" "package-add:add existing or new package to the build queue"
"package-changes:retrieve package changes stored in database"
"package-changes-remove:remove the package changes stored remotely"
"package-remove:remove package from the repository" "package-remove:remove package from the repository"
"package-status:request status of the package" "package-status:request status of the package"
"package-status-remove:remove the package from the status page" "package-status-remove:remove the package from the status page"
@ -117,6 +119,7 @@ _shtab_ahriman_aur_search_options=(
_shtab_ahriman_check_options=( _shtab_ahriman_check_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--changes,--no-changes}"[calculate changes from the latest known commit if available. Only applicable in dry run mode (default\: True)]:changes:"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: True)]:vcs:" {--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: True)]:vcs:"
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]"
@ -149,7 +152,9 @@ _shtab_ahriman_daemon_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:" {-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:" {--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:"
{--changes,--no-changes}"[calculate changes from the latest known commit if available. Only applicable in dry run mode (default\: True)]:changes:"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: True)]:dependencies:" {--dependencies,--no-dependencies}"[process missing package dependencies (default\: True)]:dependencies:"
"--dry-run[just perform check for updates, same as check command (default\: False)]"
{--local,--no-local}"[enable or disable checking of local packages for updates (default\: True)]:local:" {--local,--no-local}"[enable or disable checking of local packages for updates (default\: True)]:local:"
{--manual,--no-manual}"[include or exclude manual updates (default\: True)]:manual:" {--manual,--no-manual}"[include or exclude manual updates (default\: True)]:manual:"
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: True)]:vcs:" {--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: True)]:vcs:"
@ -210,6 +215,17 @@ _shtab_ahriman_package_add_options=(
"(*):package source (base name, path to local files, remote URL):" "(*):package source (base name, path to local files, remote URL):"
) )
_shtab_ahriman_package_changes_options=(
"(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
":package base:"
)
_shtab_ahriman_package_changes_remove_options=(
"(- : *)"{-h,--help}"[show this help message and exit]"
":package base:"
)
_shtab_ahriman_package_remove_options=( _shtab_ahriman_package_remove_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*):package name or base:" "(*):package name or base:"
@ -302,6 +318,7 @@ _shtab_ahriman_repo_backup_options=(
_shtab_ahriman_repo_check_options=( _shtab_ahriman_repo_check_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--changes,--no-changes}"[calculate changes from the latest known commit if available. Only applicable in dry run mode (default\: True)]:changes:"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: True)]:vcs:" {--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: True)]:vcs:"
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]"
@ -342,7 +359,9 @@ _shtab_ahriman_repo_daemon_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:" {-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:" {--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:"
{--changes,--no-changes}"[calculate changes from the latest known commit if available. Only applicable in dry run mode (default\: True)]:changes:"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: True)]:dependencies:" {--dependencies,--no-dependencies}"[process missing package dependencies (default\: True)]:dependencies:"
"--dry-run[just perform check for updates, same as check command (default\: False)]"
{--local,--no-local}"[enable or disable checking of local packages for updates (default\: True)]:local:" {--local,--no-local}"[enable or disable checking of local packages for updates (default\: True)]:local:"
{--manual,--no-manual}"[include or exclude manual updates (default\: True)]:manual:" {--manual,--no-manual}"[include or exclude manual updates (default\: True)]:manual:"
{--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: True)]:vcs:" {--vcs,--no-vcs}"[fetch actual version of VCS packages (default\: True)]:vcs:"
@ -434,6 +453,7 @@ _shtab_ahriman_repo_triggers_options=(
_shtab_ahriman_repo_update_options=( _shtab_ahriman_repo_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:" {--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:"
{--changes,--no-changes}"[calculate changes from the latest known commit if available. Only applicable in dry run mode (default\: True)]:changes:"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: True)]:dependencies:" {--dependencies,--no-dependencies}"[process missing package dependencies (default\: True)]:dependencies:"
"--dry-run[just perform check for updates, same as check command (default\: False)]" "--dry-run[just perform check for updates, same as check command (default\: False)]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
@ -574,6 +594,7 @@ _shtab_ahriman_sync_options=(
_shtab_ahriman_update_options=( _shtab_ahriman_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:" {--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:"
{--changes,--no-changes}"[calculate changes from the latest known commit if available. Only applicable in dry run mode (default\: True)]:changes:"
{--dependencies,--no-dependencies}"[process missing package dependencies (default\: True)]:dependencies:" {--dependencies,--no-dependencies}"[process missing package dependencies (default\: True)]:dependencies:"
"--dry-run[just perform check for updates, same as check command (default\: False)]" "--dry-run[just perform check for updates, same as check command (default\: False)]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
@ -644,6 +665,8 @@ _shtab_ahriman() {
init) _arguments -C -s $_shtab_ahriman_init_options ;; init) _arguments -C -s $_shtab_ahriman_init_options ;;
key-import) _arguments -C -s $_shtab_ahriman_key_import_options ;; key-import) _arguments -C -s $_shtab_ahriman_key_import_options ;;
package-add) _arguments -C -s $_shtab_ahriman_package_add_options ;; package-add) _arguments -C -s $_shtab_ahriman_package_add_options ;;
package-changes) _arguments -C -s $_shtab_ahriman_package_changes_options ;;
package-changes-remove) _arguments -C -s $_shtab_ahriman_package_changes_remove_options ;;
package-remove) _arguments -C -s $_shtab_ahriman_package_remove_options ;; package-remove) _arguments -C -s $_shtab_ahriman_package_remove_options ;;
package-status) _arguments -C -s $_shtab_ahriman_package_status_options ;; package-status) _arguments -C -s $_shtab_ahriman_package_status_options ;;
package-status-remove) _arguments -C -s $_shtab_ahriman_package_status_remove_options ;; package-status-remove) _arguments -C -s $_shtab_ahriman_package_status_remove_options ;;

View File

@ -101,6 +101,8 @@ def _parser() -> argparse.ArgumentParser:
_set_help_updates_parser(subparsers) _set_help_updates_parser(subparsers)
_set_help_version_parser(subparsers) _set_help_version_parser(subparsers)
_set_package_add_parser(subparsers) _set_package_add_parser(subparsers)
_set_package_changes_parser(subparsers)
_set_package_changes_remove_parser(subparsers)
_set_package_remove_parser(subparsers) _set_package_remove_parser(subparsers)
_set_package_status_parser(subparsers) _set_package_status_parser(subparsers)
_set_package_status_remove_parser(subparsers) _set_package_status_remove_parser(subparsers)
@ -281,6 +283,44 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
return parser return parser
def _set_package_changes_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package changes subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-changes", help="get package changes",
description="retrieve package changes stored in database",
epilog="This feature requests package status from the web interface if it is available.",
formatter_class=_formatter)
parser.add_argument("package", help="package base")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.set_defaults(handler=handlers.Change, action=Action.List, lock=None, quiet=True, report=False, unsafe=True)
return parser
def _set_package_changes_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package change remove subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-changes-remove", help="remove package changes",
description="remove the package changes stored remotely",
formatter_class=_formatter)
parser.add_argument("package", help="package base")
parser.set_defaults(handler=handlers.Change, action=Action.Remove, lock=None, quiet=True, report=False, unsafe=True)
return parser
def _set_package_remove_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_package_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for package removal subcommand add parser for package removal subcommand
@ -493,6 +533,9 @@ def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
description="check for packages updates. Same as repo-update --dry-run --no-manual", description="check for packages updates. Same as repo-update --dry-run --no-manual",
formatter_class=_formatter) formatter_class=_formatter)
parser.add_argument("package", help="filter check by package base", nargs="*") parser.add_argument("package", help="filter check by package base", nargs="*")
parser.add_argument("--changes", help="calculate changes from the latest known commit if available. "
"Only applicable in dry run mode",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("--vcs", help="fetch actual version of VCS packages", parser.add_argument("--vcs", help="fetch actual version of VCS packages",
action=argparse.BooleanOptionalAction, default=True) action=argparse.BooleanOptionalAction, default=True)
@ -558,8 +601,12 @@ def _set_repo_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("-i", "--interval", help="interval between runs in seconds", type=int, default=60 * 60 * 12) parser.add_argument("-i", "--interval", help="interval between runs in seconds", type=int, default=60 * 60 * 12)
parser.add_argument("--aur", help="enable or disable checking for AUR updates", parser.add_argument("--aur", help="enable or disable checking for AUR updates",
action=argparse.BooleanOptionalAction, default=True) action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--changes", help="calculate changes from the latest known commit if available. "
"Only applicable in dry run mode",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--dependencies", help="process missing package dependencies", parser.add_argument("--dependencies", help="process missing package dependencies",
action=argparse.BooleanOptionalAction, default=True) action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true")
parser.add_argument("--local", help="enable or disable checking of local packages for updates", parser.add_argument("--local", help="enable or disable checking of local packages for updates",
action=argparse.BooleanOptionalAction, default=True) action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--manual", help="include or exclude manual updates", parser.add_argument("--manual", help="include or exclude manual updates",
@ -569,7 +616,7 @@ def _set_repo_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, " parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
"-yy to force refresh even if up to date", "-yy to force refresh even if up to date",
action="count", default=False) action="count", default=False)
parser.set_defaults(handler=handlers.Daemon, dry_run=False, exit_code=False, package=[]) parser.set_defaults(handler=handlers.Daemon, exit_code=False, package=[])
return parser return parser
@ -769,6 +816,9 @@ def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("package", help="filter check by package base", nargs="*") parser.add_argument("package", help="filter check by package base", nargs="*")
parser.add_argument("--aur", help="enable or disable checking for AUR updates", parser.add_argument("--aur", help="enable or disable checking for AUR updates",
action=argparse.BooleanOptionalAction, default=True) action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--changes", help="calculate changes from the latest known commit if available. "
"Only applicable in dry run mode",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--dependencies", help="process missing package dependencies", parser.add_argument("--dependencies", help="process missing package dependencies",
action=argparse.BooleanOptionalAction, default=True) action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true") parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true")

View File

@ -150,7 +150,7 @@ class Application(ApplicationPackages, ApplicationRepository):
with_dependencies[package.base] = package with_dependencies[package.base] = package
# register package in local database # register package in local database
self.database.remote_update(package) self.database.package_base_update(package)
self.repository.reporter.set_unknown(package) self.repository.reporter.set_unknown(package)
return list(with_dependencies.values()) return list(with_dependencies.values())

View File

@ -65,7 +65,7 @@ class ApplicationPackages(ApplicationProperties):
""" """
package = Package.from_aur(source, username) package = Package.from_aur(source, username)
self.database.build_queue_insert(package) self.database.build_queue_insert(package)
self.database.remote_update(package) self.database.package_base_update(package)
def _add_directory(self, source: str, *_: Any) -> None: def _add_directory(self, source: str, *_: Any) -> None:
""" """
@ -139,7 +139,7 @@ class ApplicationPackages(ApplicationProperties):
""" """
package = Package.from_official(source, self.repository.pacman, username) package = Package.from_official(source, self.repository.pacman, username)
self.database.build_queue_insert(package) self.database.build_queue_insert(package)
self.database.remote_update(package) self.database.package_base_update(package)
def add(self, names: Iterable[str], source: PackageSource, username: str | None = None) -> None: def add(self, names: Iterable[str], source: PackageSource, username: str | None = None) -> None:
""" """

View File

@ -33,6 +33,23 @@ class ApplicationRepository(ApplicationProperties):
repository control class repository control class
""" """
def changes(self, packages: Iterable[Package]) -> None:
"""
generate and update package changes
Args:
packages(Iterable[Package]): list of packages to retrieve changes
"""
last_commit_hashes = self.database.hashes_get()
for package in packages:
last_commit_sha = last_commit_hashes.get(package.base)
if last_commit_sha is None:
continue # skip check in case if we can't calculate diff
changes = self.repository.package_changes(package, last_commit_sha)
self.repository.reporter.package_changes_set(package.base, changes)
def clean(self, *, cache: bool, chroot: bool, manual: bool, packages: bool, pacman: bool) -> None: def clean(self, *, cache: bool, chroot: bool, manual: bool, packages: bool, pacman: bool) -> None:
""" """
run all clean methods. Warning: some functions might not be available under non-root run all clean methods. Warning: some functions might not be available under non-root

View File

@ -21,6 +21,7 @@ from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers.add import Add from ahriman.application.handlers.add import Add
from ahriman.application.handlers.backup import Backup from ahriman.application.handlers.backup import Backup
from ahriman.application.handlers.change import Change
from ahriman.application.handlers.clean import Clean from ahriman.application.handlers.clean import Clean
from ahriman.application.handlers.daemon import Daemon from ahriman.application.handlers.daemon import Daemon
from ahriman.application.handlers.dump import Dump from ahriman.application.handlers.dump import Dump

View File

@ -0,0 +1,59 @@
#
# Copyright (c) 2021-2023 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import argparse
from ahriman.application.application import Application
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.formatters import ChangesPrinter
from ahriman.models.action import Action
from ahriman.models.changes import Changes
from ahriman.models.repository_id import RepositoryId
class Change(Handler):
"""
package changes handler
"""
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
"""
application = Application(repository_id, configuration, report=True)
client = application.repository.reporter
match args.action:
case Action.List:
changes = client.package_changes_get(args.package)
ChangesPrinter(changes)(verbose=True, separator="")
Change.check_if_empty(args.exit_code, changes.is_empty)
case Action.Remove:
client.package_changes_set(args.package, Changes())

View File

@ -47,9 +47,13 @@ class Update(Handler):
""" """
application = Application(repository_id, configuration, report=report, refresh_pacman_database=args.refresh) application = Application(repository_id, configuration, report=report, refresh_pacman_database=args.refresh)
application.on_start() application.on_start()
packages = application.updates(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs) packages = application.updates(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs)
Update.check_if_empty(args.exit_code, not packages) if args.dry_run: # some check specific actions
if args.dry_run: if args.changes: # generate changes if requested
application.changes(packages)
Update.check_if_empty(args.exit_code, not packages) # status code check
return return
packages = application.with_dependencies(packages, process_dependencies=args.dependencies) packages = application.with_dependencies(packages, process_dependencies=args.dependencies)

View File

@ -21,6 +21,7 @@ import shutil
from pathlib import Path from pathlib import Path
from ahriman.core.exceptions import CalledProcessError
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
from ahriman.core.util import check_output, utcnow, walk from ahriman.core.util import check_output, utcnow, walk
from ahriman.models.package import Package from ahriman.models.package import Package
@ -42,6 +43,25 @@ class Sources(LazyLogging):
DEFAULT_BRANCH = "master" # default fallback branch DEFAULT_BRANCH = "master" # default fallback branch
DEFAULT_COMMIT_AUTHOR = ("ahriman", "ahriman@localhost") DEFAULT_COMMIT_AUTHOR = ("ahriman", "ahriman@localhost")
@staticmethod
def changes(source_dir: Path, last_commit_sha: str | None) -> str | None:
"""
extract changes from the last known commit if available
Args:
source_dir(Path): local path to directory with source files
last_commit_sha(str | None): last known commit hash
Returns:
str | None: changes from the last commit if available or ``None`` otherwise
"""
if last_commit_sha is None:
return None # no previous reference found
instance = Sources()
instance.fetch_until(source_dir, commit_sha=last_commit_sha)
return instance.diff(source_dir, last_commit_sha)
@staticmethod @staticmethod
def extend_architectures(sources_dir: Path, architecture: str) -> list[PkgbuildPatch]: def extend_architectures(sources_dir: Path, architecture: str) -> list[PkgbuildPatch]:
""" """
@ -61,13 +81,16 @@ class Sources(LazyLogging):
return [PkgbuildPatch("arch", list(architectures))] return [PkgbuildPatch("arch", list(architectures))]
@staticmethod @staticmethod
def fetch(sources_dir: Path, remote: RemoteSource) -> None: def fetch(sources_dir: Path, remote: RemoteSource) -> str | None:
""" """
either clone repository or update it to origin/``remote.branch`` either clone repository or update it to origin/``remote.branch``
Args: Args:
sources_dir(Path): local path to fetch sources_dir(Path): local path to fetch
remote(RemoteSource): remote target (from where to fetch) remote(RemoteSource): remote target (from where to fetch)
Returns:
str | None: current commit sha if available
""" """
instance = Sources() instance = Sources()
# local directory exists and there is .git directory # local directory exists and there is .git directory
@ -75,13 +98,12 @@ class Sources(LazyLogging):
if is_initialized_git and not instance.has_remotes(sources_dir): if is_initialized_git and not instance.has_remotes(sources_dir):
# there is git repository, but no remote configured so far # there is git repository, but no remote configured so far
instance.logger.info("skip update at %s because there are no branches configured", sources_dir) instance.logger.info("skip update at %s because there are no branches configured", sources_dir)
return return instance.head(sources_dir)
branch = remote.branch or instance.DEFAULT_BRANCH branch = remote.branch or instance.DEFAULT_BRANCH
if is_initialized_git: if is_initialized_git:
instance.logger.info("update HEAD to remote at %s using branch %s", sources_dir, branch) instance.logger.info("update HEAD to remote at %s using branch %s", sources_dir, branch)
check_output("git", "fetch", "--quiet", "--depth", "1", "origin", branch, instance.fetch_until(sources_dir, branch=branch)
cwd=sources_dir, logger=instance.logger)
elif remote.git_url is not None: elif remote.git_url is not None:
instance.logger.info("clone remote %s to %s using branch %s", remote.git_url, sources_dir, branch) instance.logger.info("clone remote %s to %s using branch %s", remote.git_url, sources_dir, branch)
check_output("git", "clone", "--quiet", "--depth", "1", "--branch", branch, "--single-branch", check_output("git", "clone", "--quiet", "--depth", "1", "--branch", branch, "--single-branch",
@ -100,6 +122,8 @@ class Sources(LazyLogging):
pkgbuild_dir = remote.pkgbuild_dir or sources_dir.resolve() pkgbuild_dir = remote.pkgbuild_dir or sources_dir.resolve()
instance.move((sources_dir / pkgbuild_dir).resolve(), sources_dir) instance.move((sources_dir / pkgbuild_dir).resolve(), sources_dir)
return instance.head(sources_dir)
@staticmethod @staticmethod
def has_remotes(sources_dir: Path) -> bool: def has_remotes(sources_dir: Path) -> bool:
""" """
@ -136,7 +160,7 @@ class Sources(LazyLogging):
instance.commit(sources_dir) instance.commit(sources_dir)
@staticmethod @staticmethod
def load(sources_dir: Path, package: Package, patches: list[PkgbuildPatch], paths: RepositoryPaths) -> None: def load(sources_dir: Path, package: Package, patches: list[PkgbuildPatch], paths: RepositoryPaths) -> str | None:
""" """
fetch sources from remote and apply patches fetch sources from remote and apply patches
@ -145,17 +169,22 @@ class Sources(LazyLogging):
package(Package): package definitions package(Package): package definitions
patches(list[PkgbuildPatch]): optional patch to be applied patches(list[PkgbuildPatch]): optional patch to be applied
paths(RepositoryPaths): repository paths instance paths(RepositoryPaths): repository paths instance
Returns:
str | None: current commit sha if available
""" """
instance = Sources() instance = Sources()
if (cache_dir := paths.cache_for(package.base)).is_dir() and cache_dir != sources_dir: if (cache_dir := paths.cache_for(package.base)).is_dir() and cache_dir != sources_dir:
# no need to clone whole repository, just copy from cache first # no need to clone whole repository, just copy from cache first
shutil.copytree(cache_dir, sources_dir, dirs_exist_ok=True) shutil.copytree(cache_dir, sources_dir, dirs_exist_ok=True)
instance.fetch(sources_dir, package.remote) last_commit_sha = instance.fetch(sources_dir, package.remote)
patches.extend(instance.extend_architectures(sources_dir, paths.repository_id.architecture)) patches.extend(instance.extend_architectures(sources_dir, paths.repository_id.architecture))
for patch in patches: for patch in patches:
instance.patch_apply(sources_dir, patch) instance.patch_apply(sources_dir, patch)
return last_commit_sha
@staticmethod @staticmethod
def patch_create(sources_dir: Path, *pattern: str) -> str: def patch_create(sources_dir: Path, *pattern: str) -> str:
""" """
@ -247,17 +276,47 @@ class Sources(LazyLogging):
return True return True
def diff(self, sources_dir: Path) -> str: def diff(self, sources_dir: Path, sha: str | None = None) -> str:
""" """
generate diff from the current version and write it to the output file generate diff from the current version and write it to the output file
Args: Args:
sources_dir(Path): local path to git repository sources_dir(Path): local path to git repository
sha(str | None, optional): optional commit sha to calculate diff (Default value = None)
Returns: Returns:
str: patch as plain string str: patch as plain string
""" """
return check_output("git", "diff", cwd=sources_dir, logger=self.logger) args = []
if sha is not None:
args.append(sha)
return check_output("git", "diff", *args, cwd=sources_dir, logger=self.logger)
def fetch_until(self, sources_dir: Path, *, branch: str | None = None, commit_sha: str | None = None) -> None:
"""
fetch repository until commit sha
Args:
sources_dir(Path): local path to git repository
branch(str | None, optional): use specified branch (Default value = None)
commit_sha(str | None, optional): commit hash to fetch. If none set, only one will be fetched
(Default value = None)
"""
commit_sha = commit_sha or "HEAD" # if none set we just fetch the last commit
commits_count = 1
while commit_sha is not None:
command = ["git", "fetch", "--quiet", "--depth", str(commits_count)]
if branch is not None:
command += ["origin", branch]
check_output(*command, cwd=sources_dir, logger=self.logger) # fetch one more level
try:
# check if there is an object in current git directory
check_output("git", "cat-file", "-e", commit_sha, cwd=sources_dir, logger=self.logger)
commit_sha = None # reset search
except CalledProcessError:
commits_count += 1 # increase depth
def has_changes(self, sources_dir: Path) -> bool: def has_changes(self, sources_dir: Path) -> bool:
""" """
@ -273,6 +332,20 @@ class Sources(LazyLogging):
changes = check_output("git", "diff", "--cached", "--name-only", cwd=sources_dir, logger=self.logger) changes = check_output("git", "diff", "--cached", "--name-only", cwd=sources_dir, logger=self.logger)
return bool(changes) return bool(changes)
def head(self, sources_dir: Path, ref_name: str = "HEAD") -> str:
"""
extract HEAD reference for the current git repository
Args:
sources_dir(Path): local path to git repository
ref_name(str, optional): reference name (Default value = "HEAD")
Returns:
str: HEAD commit hash
"""
# we might want to parse git files instead though
return check_output("git", "rev-parse", ref_name, cwd=sources_dir)
def move(self, pkgbuild_dir: Path, sources_dir: Path) -> None: def move(self, pkgbuild_dir: Path, sources_dir: Path) -> None:
""" """
move content from pkgbuild_dir to sources_dir move content from pkgbuild_dir to sources_dir

View File

@ -109,7 +109,7 @@ class Task(LazyLogging):
).splitlines() ).splitlines()
return [Path(package) for package in packages] return [Path(package) for package in packages]
def init(self, sources_dir: Path, database: SQLite, local_version: str | None) -> None: def init(self, sources_dir: Path, database: SQLite, local_version: str | None) -> str | None:
""" """
fetch package from git fetch package from git
@ -118,10 +118,13 @@ class Task(LazyLogging):
database(SQLite): database instance database(SQLite): database instance
local_version(str | None): local version of the package. If set and equal to current version, it will local_version(str | None): local version of the package. If set and equal to current version, it will
automatically bump pkgrel automatically bump pkgrel
Returns:
str | None: current commit sha if available
""" """
Sources.load(sources_dir, self.package, database.patches_get(self.package.base), self.paths) last_commit_sha = Sources.load(sources_dir, self.package, database.patches_get(self.package.base), self.paths)
if local_version is None: if local_version is None:
return # there is no local package or pkgrel increment is disabled return last_commit_sha # there is no local package or pkgrel increment is disabled
# load fresh package # load fresh package
loaded_package = Package.from_build(sources_dir, self.architecture, None) loaded_package = Package.from_build(sources_dir, self.architecture, None)
@ -129,3 +132,5 @@ class Task(LazyLogging):
self.logger.info("package %s is the same as in repo, bumping pkgrel to %s", self.package.base, pkgrel) self.logger.info("package %s is the same as in repo, bumping pkgrel to %s", self.package.base, pkgrel)
patch = PkgbuildPatch("pkgrel", pkgrel) patch = PkgbuildPatch("pkgrel", pkgrel)
patch.write(sources_dir / "PKGBUILD") patch.write(sources_dir / "PKGBUILD")
return last_commit_sha

View File

@ -0,0 +1,33 @@
#
# Copyright (c) 2021-2023 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__all__ = ["steps"]
steps = [
"""
create table package_changes (
package_base text not null,
repository text not null,
last_commit_sha text not null,
changes text,
unique (package_base, repository)
)
""",
]

View File

@ -21,6 +21,7 @@ from ahriman.core.database.operations.operations import Operations
from ahriman.core.database.operations.auth_operations import AuthOperations from ahriman.core.database.operations.auth_operations import AuthOperations
from ahriman.core.database.operations.build_operations import BuildOperations from ahriman.core.database.operations.build_operations import BuildOperations
from ahriman.core.database.operations.changes_operations import ChangesOperations
from ahriman.core.database.operations.logs_operations import LogsOperations from ahriman.core.database.operations.logs_operations import LogsOperations
from ahriman.core.database.operations.package_operations import PackageOperations from ahriman.core.database.operations.package_operations import PackageOperations
from ahriman.core.database.operations.patch_operations import PatchOperations from ahriman.core.database.operations.patch_operations import PatchOperations

View File

@ -0,0 +1,143 @@
#
# Copyright (c) 2021-2023 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from sqlite3 import Connection
from ahriman.core.database.operations import Operations
from ahriman.models.changes import Changes
from ahriman.models.repository_id import RepositoryId
class ChangesOperations(Operations):
"""
operations for source files changes
"""
def changes_get(self, package_base: str, repository_id: RepositoryId | None = None) -> Changes:
"""
get changes for the specific package base if available
Args:
package_base(str): package base to search
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
Returns:
Changes: changes for the package base if available
"""
repository_id = repository_id or self._repository_id
def run(connection: Connection) -> Changes:
return next(
(
Changes(row["last_commit_sha"], row["changes"] or None)
for row in connection.execute(
"""
select last_commit_sha, changes from package_changes
where package_base = :package_base and repository = :repository
""",
{
"package_base": package_base,
"repository": repository_id.id,
}
)
),
Changes()
)
return self.with_connection(run)
def changes_insert(self, package_base: str, changes: Changes, repository_id: RepositoryId | None = None) -> None:
"""
insert packages to build queue
Args:
package_base(str): package base to insert
changes(Changes): package changes (as in patch format)
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
"""
repository_id = repository_id or self._repository_id
def run(connection: Connection) -> None:
connection.execute(
"""
insert into package_changes
(package_base, last_commit_sha, changes, repository)
values
(:package_base, :last_commit_sha, :changes ,:repository)
on conflict (package_base, repository) do update set
last_commit_sha = :last_commit_sha, changes = :changes
""",
{
"package_base": package_base,
"last_commit_sha": changes.last_commit_sha,
"changes": changes.changes,
"repository": repository_id.id,
})
if changes.last_commit_sha is None:
return self.changes_remove(package_base, repository_id)
return self.with_connection(run, commit=True)
def changes_remove(self, package_base: str | None, repository_id: RepositoryId | None = None) -> None:
"""
remove packages changes
Args:
package_base(str | None): optional filter by package base
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
"""
repository_id = repository_id or self._repository_id
def run(connection: Connection) -> None:
connection.execute(
"""
delete from package_changes
where (:package_base is null or package_base = :package_base)
and repository = :repository
""",
{
"package_base": package_base,
"repository": repository_id.id,
})
return self.with_connection(run, commit=True)
def hashes_get(self, repository_id: RepositoryId | None = None) -> dict[str, str]:
"""
extract last commit hashes if available
Args:
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
Returns:
dict[str, str]: map of package base to its last commit hash
"""
repository_id = repository_id or self._repository_id
def run(connection: Connection) -> dict[str, str]:
return {
row["package_base"]: row["last_commit_sha"]
for row in connection.execute(
"""select package_base, last_commit_sha from package_changes where repository = :repository""",
{"repository": repository_id.id}
)
}
return self.with_connection(run)

View File

@ -246,6 +246,21 @@ class PackageOperations(Operations):
) )
} }
def package_base_update(self, package: Package, repository_id: RepositoryId | None = None) -> None:
"""
update package base only
Args:
package(Package): package properties
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
"""
repository_id = repository_id or self._repository_id
def run(connection: Connection) -> None:
self._package_update_insert_base(connection, package, repository_id)
return self.with_connection(run, commit=True)
def package_remove(self, package_base: str, repository_id: RepositoryId | None = None) -> None: def package_remove(self, package_base: str, repository_id: RepositoryId | None = None) -> None:
""" """
remove package from database remove package from database
@ -302,21 +317,6 @@ class PackageOperations(Operations):
return self.with_connection(lambda connection: list(run(connection))) return self.with_connection(lambda connection: list(run(connection)))
def remote_update(self, package: Package, repository_id: RepositoryId | None = None) -> None:
"""
update package remote source
Args:
package(Package): package properties
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
"""
repository_id = repository_id or self._repository_id
def run(connection: Connection) -> None:
self._package_update_insert_base(connection, package, repository_id)
return self.with_connection(run, commit=True)
def remotes_get(self, repository_id: RepositoryId | None = None) -> dict[str, RemoteSource]: def remotes_get(self, repository_id: RepositoryId | None = None) -> dict[str, RemoteSource]:
""" """
get packages remotes based on current settings get packages remotes based on current settings

View File

@ -25,11 +25,12 @@ from typing import Self
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.database.migrations import Migrations from ahriman.core.database.migrations import Migrations
from ahriman.core.database.operations import AuthOperations, BuildOperations, LogsOperations, PackageOperations, \ from ahriman.core.database.operations import AuthOperations, BuildOperations, ChangesOperations, LogsOperations, \
PatchOperations PackageOperations, PatchOperations
class SQLite(AuthOperations, BuildOperations, LogsOperations, PackageOperations, PatchOperations): # pylint: disable=too-many-ancestors
class SQLite(AuthOperations, BuildOperations, ChangesOperations, LogsOperations, PackageOperations, PatchOperations):
""" """
wrapper for sqlite3 database wrapper for sqlite3 database

View File

@ -18,16 +18,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from ahriman.core.formatters.printer import Printer from ahriman.core.formatters.printer import Printer
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters.aur_printer import AurPrinter from ahriman.core.formatters.aur_printer import AurPrinter
from ahriman.core.formatters.build_printer import BuildPrinter from ahriman.core.formatters.build_printer import BuildPrinter
from ahriman.core.formatters.changes_printer import ChangesPrinter
from ahriman.core.formatters.configuration_paths_printer import ConfigurationPathsPrinter from ahriman.core.formatters.configuration_paths_printer import ConfigurationPathsPrinter
from ahriman.core.formatters.configuration_printer import ConfigurationPrinter from ahriman.core.formatters.configuration_printer import ConfigurationPrinter
from ahriman.core.formatters.package_printer import PackagePrinter from ahriman.core.formatters.package_printer import PackagePrinter
from ahriman.core.formatters.patch_printer import PatchPrinter from ahriman.core.formatters.patch_printer import PatchPrinter
from ahriman.core.formatters.repository_printer import RepositoryPrinter from ahriman.core.formatters.repository_printer import RepositoryPrinter
from ahriman.core.formatters.status_printer import StatusPrinter from ahriman.core.formatters.status_printer import StatusPrinter
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.formatters.tree_printer import TreePrinter from ahriman.core.formatters.tree_printer import TreePrinter
from ahriman.core.formatters.update_printer import UpdatePrinter from ahriman.core.formatters.update_printer import UpdatePrinter
from ahriman.core.formatters.user_printer import UserPrinter from ahriman.core.formatters.user_printer import UserPrinter

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.core.util import pretty_datetime from ahriman.core.util import pretty_datetime
from ahriman.models.aur_package import AURPackage from ahriman.models.aur_package import AURPackage
from ahriman.models.property import Property from ahriman.models.property import Property

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.package import Package from ahriman.models.package import Package

View File

@ -0,0 +1,64 @@
#
# Copyright (c) 2021-2023 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.formatters import Printer
from ahriman.models.changes import Changes
from ahriman.models.property import Property
class ChangesPrinter(Printer):
"""
print content of the changes object
Attributes:
changes(Changes): package changes
"""
def __init__(self, changes: Changes) -> None:
"""
default constructor
Args:
changes(Changes): package changes
"""
Printer.__init__(self)
self.changes = changes
def properties(self) -> list[Property]:
"""
convert content into printable data
Returns:
list[Property]: list of content properties
"""
if self.changes.is_empty:
return []
return [Property("", self.changes.changes, is_required=True, indent=0)]
# pylint: disable=redundant-returns-doc
def title(self) -> str | None:
"""
generate entry title from content
Returns:
str | None: content title if it can be generated and None otherwise
"""
if self.changes.is_empty:
return None
return self.changes.last_commit_sha

View File

@ -19,7 +19,7 @@
# #
from pathlib import Path from pathlib import Path
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.property import Property from ahriman.models.property import Property

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.property import Property from ahriman.models.property import Property

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.build_status import BuildStatus from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.property import Property from ahriman.models.property import Property

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.property import Property from ahriman.models.property import Property

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.property import Property from ahriman.models.property import Property
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.build_status import BuildStatus from ahriman.models.build_status import BuildStatus

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.property import Property from ahriman.models.property import Property

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.property import Property from ahriman.models.property import Property

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.property import Property from ahriman.models.property import Property
from ahriman.models.user import User from ahriman.models.user import User

View File

@ -20,7 +20,7 @@
from collections.abc import Generator from collections.abc import Generator
from typing import Any from typing import Any
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.property import Property from ahriman.models.property import Property

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.property import Property from ahriman.models.property import Property

View File

@ -25,45 +25,20 @@ from tempfile import TemporaryDirectory
from ahriman.core.build_tools.task import Task from ahriman.core.build_tools.task import Task
from ahriman.core.repository.cleaner import Cleaner from ahriman.core.repository.cleaner import Cleaner
from ahriman.core.repository.package_info import PackageInfo
from ahriman.core.util import safe_filename from ahriman.core.util import safe_filename
from ahriman.models.changes import Changes
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_description import PackageDescription from ahriman.models.package_description import PackageDescription
from ahriman.models.packagers import Packagers from ahriman.models.packagers import Packagers
from ahriman.models.result import Result from ahriman.models.result import Result
class Executor(Cleaner): class Executor(PackageInfo, Cleaner):
""" """
trait for common repository update processes trait for common repository update processes
""" """
def load_archives(self, packages: Iterable[Path]) -> list[Package]:
"""
load packages from list of archives
Args:
packages(Iterable[Path]): paths to package archives
Returns:
list[Package]: list of read packages
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def packages(self) -> list[Package]:
"""
generate list of repository packages
Returns:
list[Package]: list of packages properties
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def process_build(self, updates: Iterable[Package], packagers: Packagers | None = None, *, def process_build(self, updates: Iterable[Package], packagers: Packagers | None = None, *,
bump_pkgrel: bool = False) -> Result: bump_pkgrel: bool = False) -> Result:
""" """
@ -78,16 +53,18 @@ class Executor(Cleaner):
Returns: Returns:
Result: build result Result: build result
""" """
def build_single(package: Package, local_path: Path, packager_id: str | None) -> None: def build_single(package: Package, local_path: Path, packager_id: str | None) -> str | None:
self.reporter.set_building(package.base) self.reporter.set_building(package.base)
task = Task(package, self.configuration, self.architecture, self.paths) task = Task(package, self.configuration, self.architecture, self.paths)
local_version = local_versions.get(package.base) if bump_pkgrel else None local_version = local_versions.get(package.base) if bump_pkgrel else None
task.init(local_path, self.database, local_version) commit_sha = task.init(local_path, self.database, local_version)
built = task.build(local_path, PACKAGER=packager_id) built = task.build(local_path, PACKAGER=packager_id)
for src in built: for src in built:
dst = self.paths.packages / src.name dst = self.paths.packages / src.name
shutil.move(src, dst) shutil.move(src, dst)
return commit_sha
packagers = packagers or Packagers() packagers = packagers or Packagers()
local_versions = {package.base: package.version for package in self.packages()} local_versions = {package.base: package.version for package in self.packages()}
@ -97,7 +74,9 @@ class Executor(Cleaner):
TemporaryDirectory(ignore_cleanup_errors=True) as dir_name: TemporaryDirectory(ignore_cleanup_errors=True) as dir_name:
try: try:
packager = self.packager(packagers, single.base) packager = self.packager(packagers, single.base)
build_single(single, Path(dir_name), packager.packager_id) last_commit_sha = build_single(single, Path(dir_name), packager.packager_id)
# clear changes and update commit hash
self.reporter.package_changes_set(single.base, Changes(last_commit_sha))
result.add_updated(single) result.add_updated(single)
except Exception: except Exception:
self.reporter.set_failed(single.base) self.reporter.set_failed(single.base)
@ -122,6 +101,7 @@ class Executor(Cleaner):
self.database.build_queue_clear(package_base) self.database.build_queue_clear(package_base)
self.database.patches_remove(package_base, []) self.database.patches_remove(package_base, [])
self.database.logs_remove(package_base, None) self.database.logs_remove(package_base, None)
self.database.changes_remove(package_base)
self.reporter.package_remove(package_base) # we only update status page in case of base removal self.reporter.package_remove(package_base) # we only update status page in case of base removal
except Exception: except Exception:
self.logger.exception("could not remove base %s", package_base) self.logger.exception("could not remove base %s", package_base)

View File

@ -0,0 +1,126 @@
#
# Copyright (c) 2021-2023 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from collections.abc import Iterable
from pathlib import Path
from tempfile import TemporaryDirectory
from ahriman.core.build_tools.sources import Sources
from ahriman.core.repository.repository_properties import RepositoryProperties
from ahriman.core.util import package_like
from ahriman.models.changes import Changes
from ahriman.models.package import Package
class PackageInfo(RepositoryProperties):
"""
handler for the package information
"""
def load_archives(self, packages: Iterable[Path]) -> list[Package]:
"""
load packages from list of archives
Args:
packages(Iterable[Path]): paths to package archives
Returns:
list[Package]: list of read packages
"""
sources = self.database.remotes_get()
result: dict[str, Package] = {}
# we are iterating over bases, not single packages
for full_path in packages:
try:
local = Package.from_archive(full_path, self.pacman)
if (source := sources.get(local.base)) is not None:
local.remote = source
current = result.setdefault(local.base, local)
if current.version != local.version:
# force version to max of them
self.logger.warning("version of %s differs, found %s and %s",
current.base, current.version, local.version)
if current.is_outdated(local, self.paths, calculate_version=False):
current.version = local.version
current.packages.update(local.packages)
except Exception:
self.logger.exception("could not load package from %s", full_path)
return list(result.values())
def package_changes(self, package: Package, last_commit_sha: str | None) -> Changes:
"""
extract package change for the package since last commit if available
Args:
package(Package): package properties
last_commit_sha(str | None): last known commit hash
Returns:
Changes: changes if available
"""
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name:
dir_path = Path(dir_name)
current_commit_sha = Sources.load(dir_path, package, self.database.patches_get(package.base), self.paths)
changes: str | None = None
if current_commit_sha != last_commit_sha:
changes = Sources.changes(dir_path, last_commit_sha)
return Changes(last_commit_sha, changes)
def packages(self) -> list[Package]:
"""
generate list of repository packages
Returns:
list[Package]: list of packages properties
"""
return self.load_archives(filter(package_like, self.paths.repository.iterdir()))
def packages_built(self) -> list[Path]:
"""
get list of files in built packages directory
Returns:
list[Path]: list of filenames from the directory
"""
return list(filter(package_like, self.paths.packages.iterdir()))
def packages_depend_on(self, packages: list[Package], depends_on: Iterable[str] | None) -> list[Package]:
"""
extract list of packages which depends on specified package
Args:
packages(list[Package]): list of packages to be filtered
depends_on(Iterable[str] | None): dependencies of the packages
Returns:
list[Package]: list of repository packages which depend on specified packages
"""
if depends_on is None:
return packages # no list provided extract everything by default
depends_on = set(depends_on)
return [
package
for package in packages
if depends_on.intersection(package.full_depends(self.pacman, packages))
]

View File

@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from collections.abc import Iterable
from pathlib import Path
from typing import Self from typing import Self
from ahriman.core import _Context, context from ahriman.core import _Context, context
@ -28,9 +26,7 @@ from ahriman.core.database import SQLite
from ahriman.core.repository.executor import Executor from ahriman.core.repository.executor import Executor
from ahriman.core.repository.update_handler import UpdateHandler from ahriman.core.repository.update_handler import UpdateHandler
from ahriman.core.sign.gpg import GPG from ahriman.core.sign.gpg import GPG
from ahriman.core.util import package_like
from ahriman.models.context_key import ContextKey from ahriman.models.context_key import ContextKey
from ahriman.models.package import Package
from ahriman.models.pacman_synchronization import PacmanSynchronization from ahriman.models.pacman_synchronization import PacmanSynchronization
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId
@ -101,74 +97,3 @@ class Repository(Executor, UpdateHandler):
ctx.set(ContextKey("repository", type(self)), self) ctx.set(ContextKey("repository", type(self)), self)
context.set(ctx) context.set(ctx)
def load_archives(self, packages: Iterable[Path]) -> list[Package]:
"""
load packages from list of archives
Args:
packages(Iterable[Path]): paths to package archives
Returns:
list[Package]: list of read packages
"""
sources = self.database.remotes_get()
result: dict[str, Package] = {}
# we are iterating over bases, not single packages
for full_path in packages:
try:
local = Package.from_archive(full_path, self.pacman)
if (source := sources.get(local.base)) is not None:
local.remote = source
current = result.setdefault(local.base, local)
if current.version != local.version:
# force version to max of them
self.logger.warning("version of %s differs, found %s and %s",
current.base, current.version, local.version)
if current.is_outdated(local, self.paths, calculate_version=False):
current.version = local.version
current.packages.update(local.packages)
except Exception:
self.logger.exception("could not load package from %s", full_path)
return list(result.values())
def packages(self) -> list[Package]:
"""
generate list of repository packages
Returns:
list[Package]: list of packages properties
"""
return self.load_archives(filter(package_like, self.paths.repository.iterdir()))
def packages_built(self) -> list[Path]:
"""
get list of files in built packages directory
Returns:
list[Path]: list of filenames from the directory
"""
return list(filter(package_like, self.paths.packages.iterdir()))
def packages_depend_on(self, packages: list[Package], depends_on: Iterable[str] | None) -> list[Package]:
"""
extract list of packages which depends on specified package
Args:
packages(list[Package]): list of packages to be filtered
depends_on(Iterable[str] | None): dependencies of the packages
Returns:
list[Package]: list of repository packages which depend on specified packages
"""
if depends_on is None:
return packages # no list provided extract everything by default
depends_on = set(depends_on)
return [
package
for package in packages
if depends_on.intersection(package.full_depends(self.pacman, packages))
]

View File

@ -22,28 +22,17 @@ from collections.abc import Iterable
from ahriman.core.build_tools.sources import Sources from ahriman.core.build_tools.sources import Sources
from ahriman.core.exceptions import UnknownPackageError from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.repository.cleaner import Cleaner from ahriman.core.repository.cleaner import Cleaner
from ahriman.core.repository.package_info import PackageInfo
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource from ahriman.models.remote_source import RemoteSource
class UpdateHandler(Cleaner): class UpdateHandler(PackageInfo, Cleaner):
""" """
trait to get package update list trait to get package update list
""" """
def packages(self) -> list[Package]:
"""
generate list of repository packages
Returns:
list[Package]: list of packages properties
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def updates_aur(self, filter_packages: Iterable[str], *, vcs: bool) -> list[Package]: def updates_aur(self, filter_packages: Iterable[str], *, vcs: bool) -> list[Package]:
""" """
check AUR for updates check AUR for updates

View File

@ -23,6 +23,7 @@ import logging
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.changes import Changes
from ahriman.models.internal_status import InternalStatus from ahriman.models.internal_status import InternalStatus
from ahriman.models.log_record_id import LogRecordId from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package from ahriman.models.package import Package
@ -75,6 +76,28 @@ class Client:
status(BuildStatusEnum): current package build status status(BuildStatusEnum): current package build status
""" """
def package_changes_get(self, package_base: str) -> Changes:
"""
get package changes
Args:
package_base(str): package base to retrieve
Returns:
Changes: package changes if available and empty object otherwise
"""
del package_base
return Changes()
def package_changes_set(self, package_base: str, changes: Changes) -> None:
"""
update package changes
Args:
package_base(str): package base to update
changes(Changes): changes descriptor
"""
def package_get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]: def package_get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]:
""" """
get package status get package status

View File

@ -21,6 +21,7 @@ from ahriman.core.database import SQLite
from ahriman.core.exceptions import UnknownPackageError from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.changes import Changes
from ahriman.models.log_record_id import LogRecordId from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
@ -113,6 +114,23 @@ class Watcher(LazyLogging):
self._last_log_record_id = log_record_id self._last_log_record_id = log_record_id
self.database.logs_insert(log_record_id, created, record, self.repository_id) self.database.logs_insert(log_record_id, created, record, self.repository_id)
def package_changes_get(self, package_base: str) -> Changes:
"""
retrieve package changes
Args:
package_base(str): package base
Returns:
Changes: package changes if available
Raises:
UnknownPackageError: if no package found
"""
if package_base not in self.known:
raise UnknownPackageError(package_base)
return self.database.changes_get(package_base, self.repository_id)
def package_get(self, package_base: str) -> tuple[Package, BuildStatus]: def package_get(self, package_base: str) -> tuple[Package, BuildStatus]:
""" """
get current package base build status get current package base build status

View File

@ -26,6 +26,7 @@ from ahriman.core.configuration import Configuration
from ahriman.core.http import SyncAhrimanClient from ahriman.core.http import SyncAhrimanClient
from ahriman.core.status.client import Client from ahriman.core.status.client import Client
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.changes import Changes
from ahriman.models.internal_status import InternalStatus from ahriman.models.internal_status import InternalStatus
from ahriman.models.log_record_id import LogRecordId from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package from ahriman.models.package import Package
@ -83,6 +84,18 @@ class WebClient(Client, SyncAhrimanClient):
address = f"http://{host}:{port}" address = f"http://{host}:{port}"
return "web", address return "web", address
def _changes_url(self, package_base: str) -> str:
"""
get url for the changes api
Args:
package_base(str): package base
Returns:
str: full url for web service for logs
"""
return f"{self.address}/api/v1/packages/{package_base}/changes"
def _logs_url(self, package_base: str) -> str: def _logs_url(self, package_base: str) -> str:
""" """
get url for the logs api get url for the logs api
@ -134,6 +147,37 @@ class WebClient(Client, SyncAhrimanClient):
self.make_request("POST", self._package_url(package.base), self.make_request("POST", self._package_url(package.base),
params=self.repository_id.query(), json=payload) params=self.repository_id.query(), json=payload)
def package_changes_get(self, package_base: str) -> Changes:
"""
get package changes
Args:
package_base(str): package base to retrieve
Returns:
Changes: package changes if available and empty object otherwise
"""
with contextlib.suppress(Exception):
response = self.make_request("GET", self._changes_url(package_base),
params=self.repository_id.query())
response_json = response.json()
return Changes.from_json(response_json)
return Changes()
def package_changes_set(self, package_base: str, changes: Changes) -> None:
"""
update package changes
Args:
package_base(str): package base to update
changes(Changes): changes descriptor
"""
with contextlib.suppress(Exception):
self.make_request("POST", self._changes_url(package_base),
params=self.repository_id.query(), json=changes.view())
def package_get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]: def package_get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]:
""" """
get package status get package status

View File

@ -0,0 +1,71 @@
#
# Copyright (c) 2021-2023 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from dataclasses import dataclass, fields
from typing import Any, Self
from ahriman.core.util import dataclass_view, filter_json
@dataclass(frozen=True)
class Changes:
"""
package source files changes holder
Attributes:
last_commit_sha(str | None): last commit hash
changes(str | None): package change since the last commit if available
"""
last_commit_sha: str | None = None
changes: str | None = None
@property
def is_empty(self) -> bool:
"""
validate that changes are not empty
Returns:
bool: ``True`` in case if changes are not set and ``False`` otherwise
"""
return self.changes is None
@classmethod
def from_json(cls, dump: dict[str, Any]) -> Self:
"""
construct changes from the json dump
Args:
dump(dict[str, Any]): json dump body
Returns:
Self: changes object
"""
# filter to only known fields
known_fields = [pair.name for pair in fields(cls)]
return cls(**filter_json(dump, known_fields))
def view(self) -> dict[str, Any]:
"""
generate json change view
Returns:
dict[str, Any]: json-friendly dictionary
"""
return dataclass_view(self)

View File

@ -19,6 +19,7 @@
# #
from ahriman.web.schemas.aur_package_schema import AURPackageSchema from ahriman.web.schemas.aur_package_schema import AURPackageSchema
from ahriman.web.schemas.auth_schema import AuthSchema from ahriman.web.schemas.auth_schema import AuthSchema
from ahriman.web.schemas.changes_schema import ChangesSchema
from ahriman.web.schemas.counters_schema import CountersSchema from ahriman.web.schemas.counters_schema import CountersSchema
from ahriman.web.schemas.error_schema import ErrorSchema from ahriman.web.schemas.error_schema import ErrorSchema
from ahriman.web.schemas.file_schema import FileSchema from ahriman.web.schemas.file_schema import FileSchema

View File

@ -0,0 +1,34 @@
#
# Copyright (c) 2021-2023 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from marshmallow import Schema, fields
class ChangesSchema(Schema):
"""
response package changes schema
"""
last_commit_sha = fields.String(metadata={
"description": "Last recorded commit hash",
"example": "f1875edca1eb8fc0e55c41d1cae5fa05b6b7c6",
})
changes = fields.String(metadata={
"description": "Package changes in patch format",
})

View File

@ -0,0 +1,118 @@
#
# Copyright (c) 2021-2023 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
from ahriman.core.exceptions import UnknownPackageError
from ahriman.models.changes import Changes
from ahriman.models.user_access import UserAccess
from ahriman.web.schemas import AuthSchema, ChangesSchema, ErrorSchema, PackageNameSchema, RepositoryIdSchema
from ahriman.web.views.base import BaseView
class ChangesView(BaseView):
"""
package changes web view
Attributes:
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
GET_PERMISSION = UserAccess.Reporter
POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/packages/{package}/changes"]
@aiohttp_apispec.docs(
tags=["Packages"],
summary="Get package changes",
description="Retrieve package changes since the last build",
responses={
200: {"description": "Success response", "schema": ChangesSchema},
401: {"description": "Authorization required", "schema": ErrorSchema},
403: {"description": "Access is forbidden", "schema": ErrorSchema},
404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
)
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
async def get(self) -> Response:
"""
get package changes
Returns:
Response: 200 with package change on success
Raises:
HTTPNotFound: if package base is unknown
"""
package_base = self.request.match_info["package"]
try:
changes = self.service().package_changes_get(package_base)
except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
return json_response(changes.view())
@aiohttp_apispec.docs(
tags=["Packages"],
summary="Update package changes",
description="Update package changes to the new ones",
responses={
204: {"description": "Success response"},
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
401: {"description": "Authorization required", "schema": ErrorSchema},
403: {"description": "Access is forbidden", "schema": ErrorSchema},
404: {"description": "Repository is unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
)
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
@aiohttp_apispec.json_schema(ChangesSchema)
async def post(self) -> None:
"""
insert new package changes
Raises:
HTTPBadRequest: if bad data is supplied
HTTPNoContent: in case of success response
"""
package_base = self.request.match_info["package"]
try:
data = await self.request.json()
last_commit_sha = data.get("last_commit_sha") # empty/null meant removal
change = data.get("changes")
except Exception as ex:
raise HTTPBadRequest(reason=str(ex))
changes = Changes(last_commit_sha, change)
repository_id = self.repository_id()
self.service(repository_id).database.changes_insert(package_base, changes, repository_id)
raise HTTPNoContent

View File

@ -93,7 +93,7 @@ def test_with_dependencies(application: Application, package_ahriman: Package, p
side_effect=lambda *args: packages[args[0].name]) side_effect=lambda *args: packages[args[0].name])
packages_mock = mocker.patch("ahriman.application.application.Application._known_packages", packages_mock = mocker.patch("ahriman.application.application.Application._known_packages",
return_value={"devtools", "python-build", "python-pytest"}) return_value={"devtools", "python-build", "python-pytest"})
update_remote_mock = mocker.patch("ahriman.core.database.SQLite.remote_update") update_remote_mock = mocker.patch("ahriman.core.database.SQLite.package_base_update")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_unknown") status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_unknown")
result = application.with_dependencies([package_ahriman], process_dependencies=True) result = application.with_dependencies([package_ahriman], process_dependencies=True)

View File

@ -41,7 +41,7 @@ def test_add_aur(application_packages: ApplicationPackages, package_ahriman: Pac
""" """
mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman) mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman)
build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_insert") build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_insert")
update_remote_mock = mocker.patch("ahriman.core.database.SQLite.remote_update") update_remote_mock = mocker.patch("ahriman.core.database.SQLite.package_base_update")
application_packages._add_aur(package_ahriman.base, "packager") application_packages._add_aur(package_ahriman.base, "packager")
build_queue_mock.assert_called_once_with(package_ahriman) build_queue_mock.assert_called_once_with(package_ahriman)
@ -153,7 +153,7 @@ def test_add_repository(application_packages: ApplicationPackages, package_ahrim
""" """
mocker.patch("ahriman.models.package.Package.from_official", return_value=package_ahriman) mocker.patch("ahriman.models.package.Package.from_official", return_value=package_ahriman)
build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_insert") build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_insert")
update_remote_mock = mocker.patch("ahriman.core.database.SQLite.remote_update") update_remote_mock = mocker.patch("ahriman.core.database.SQLite.package_base_update")
application_packages._add_repository(package_ahriman.base, "packager") application_packages._add_repository(package_ahriman.base, "packager")
build_queue_mock.assert_called_once_with(package_ahriman) build_queue_mock.assert_called_once_with(package_ahriman)

View File

@ -5,16 +5,49 @@ from unittest.mock import call as MockCall
from ahriman.application.application.application_repository import ApplicationRepository from ahriman.application.application.application_repository import ApplicationRepository
from ahriman.core.tree import Leaf, Tree from ahriman.core.tree import Leaf, Tree
from ahriman.models.changes import Changes
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.packagers import Packagers from ahriman.models.packagers import Packagers
from ahriman.models.result import Result from ahriman.models.result import Result
def test_changes(application_repository: ApplicationRepository, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must generate changes for the packages
"""
changes = Changes("hash", "change")
hashes_mock = mocker.patch("ahriman.core.database.SQLite.hashes_get", return_value={
package_ahriman.base: changes.last_commit_sha,
})
changes_mock = mocker.patch("ahriman.core.repository.Repository.package_changes", return_value=changes)
report_mock = mocker.patch("ahriman.core.status.client.Client.package_changes_set")
application_repository.changes([package_ahriman])
hashes_mock.assert_called_once_with()
changes_mock.assert_called_once_with(package_ahriman, changes.last_commit_sha)
report_mock.assert_called_once_with(package_ahriman.base, changes)
def test_changes_skip(application_repository: ApplicationRepository, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must skip change generation if no last commit sha has been found
"""
mocker.patch("ahriman.core.database.SQLite.hashes_get", return_value={})
changes_mock = mocker.patch("ahriman.core.repository.Repository.package_changes")
report_mock = mocker.patch("ahriman.core.status.client.Client.package_changes_set")
application_repository.changes([package_ahriman])
changes_mock.assert_not_called()
report_mock.assert_not_called()
def test_clean_cache(application_repository: ApplicationRepository, mocker: MockerFixture) -> None: def test_clean_cache(application_repository: ApplicationRepository, mocker: MockerFixture) -> None:
""" """
must clean cache directory must clean cache directory
""" """
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_cache") clear_mock = mocker.patch("ahriman.core.repository.Repository.clear_cache")
application_repository.clean(cache=True, chroot=False, manual=False, packages=False, pacman=False) application_repository.clean(cache=True, chroot=False, manual=False, packages=False, pacman=False)
clear_mock.assert_called_once_with() clear_mock.assert_called_once_with()
@ -23,7 +56,7 @@ def test_clean_chroot(application_repository: ApplicationRepository, mocker: Moc
""" """
must clean chroot directory must clean chroot directory
""" """
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_chroot") clear_mock = mocker.patch("ahriman.core.repository.Repository.clear_chroot")
application_repository.clean(cache=False, chroot=True, manual=False, packages=False, pacman=False) application_repository.clean(cache=False, chroot=True, manual=False, packages=False, pacman=False)
clear_mock.assert_called_once_with() clear_mock.assert_called_once_with()
@ -32,7 +65,7 @@ def test_clean_manual(application_repository: ApplicationRepository, mocker: Moc
""" """
must clean manual directory must clean manual directory
""" """
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_queue") clear_mock = mocker.patch("ahriman.core.repository.Repository.clear_queue")
application_repository.clean(cache=False, chroot=False, manual=True, packages=False, pacman=False) application_repository.clean(cache=False, chroot=False, manual=True, packages=False, pacman=False)
clear_mock.assert_called_once_with() clear_mock.assert_called_once_with()
@ -41,7 +74,7 @@ def test_clean_packages(application_repository: ApplicationRepository, mocker: M
""" """
must clean packages directory must clean packages directory
""" """
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_packages") clear_mock = mocker.patch("ahriman.core.repository.Repository.clear_packages")
application_repository.clean(cache=False, chroot=False, manual=False, packages=True, pacman=False) application_repository.clean(cache=False, chroot=False, manual=False, packages=True, pacman=False)
clear_mock.assert_called_once_with() clear_mock.assert_called_once_with()
@ -50,7 +83,7 @@ def test_clean_pacman(application_repository: ApplicationRepository, mocker: Moc
""" """
must clean packages directory must clean packages directory
""" """
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_pacman") clear_mock = mocker.patch("ahriman.core.repository.Repository.clear_pacman")
application_repository.clean(cache=False, chroot=False, manual=False, packages=False, pacman=True) application_repository.clean(cache=False, chroot=False, manual=False, packages=False, pacman=True)
clear_mock.assert_called_once_with() clear_mock.assert_called_once_with()

View File

@ -0,0 +1,98 @@
import argparse
import pytest
from pytest_mock import MockerFixture
from ahriman.application.handlers import Change
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.repository import Repository
from ahriman.models.action import Action
from ahriman.models.changes import Changes
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
"""
default arguments for these test cases
Args:
args(argparse.Namespace): command line arguments fixture
Returns:
argparse.Namespace: generated arguments for these test cases
"""
args.action = Action.List
args.exit_code = False
args.package = "package"
return args
def test_run(args: argparse.Namespace, configuration: Configuration, repository: Repository,
mocker: MockerFixture) -> None:
"""
must run command
"""
args = _default_args(args)
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
application_mock = mocker.patch("ahriman.core.status.client.Client.package_changes_get",
return_value=Changes("sha", "change"))
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
print_mock = mocker.patch("ahriman.core.formatters.Printer.print")
_, repository_id = configuration.check_loaded()
Change.run(args, repository_id, configuration, report=False)
application_mock.assert_called_once_with(args.package)
check_mock.assert_called_once_with(False, False)
print_mock.assert_called_once_with(verbose=True, log_fn=pytest.helpers.anyvar(int), separator="")
def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, repository: Repository,
mocker: MockerFixture) -> None:
"""
must raise ExitCode exception on empty changes result
"""
args = _default_args(args)
args.exit_code = True
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
mocker.patch("ahriman.core.status.client.Client.package_changes_get", return_value=Changes())
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
_, repository_id = configuration.check_loaded()
Change.run(args, repository_id, configuration, report=False)
check_mock.assert_called_once_with(True, True)
def test_run_remove(args: argparse.Namespace, configuration: Configuration, repository: Repository,
mocker: MockerFixture) -> None:
"""
must remove package changes
"""
args = _default_args(args)
args.action = Action.Remove
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
update_mock = mocker.patch("ahriman.core.status.client.Client.package_changes_set")
_, repository_id = configuration.check_loaded()
Change.run(args, repository_id, configuration, report=False)
update_mock.assert_called_once_with(args.package, Changes())
def test_imply_with_report(args: argparse.Namespace, configuration: Configuration, database: SQLite,
mocker: MockerFixture) -> None:
"""
must create application object with native reporting
"""
args = _default_args(args)
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
load_mock = mocker.patch("ahriman.core.repository.Repository.load")
_, repository_id = configuration.check_loaded()
Change.run(args, repository_id, configuration, report=False)
load_mock.assert_called_once_with(repository_id, configuration, database, report=True, refresh_pacman_database=0)
def test_disallow_multi_architecture_run() -> None:
"""
must not allow multi architecture run
"""
assert not Change.ALLOW_MULTI_ARCHITECTURE_RUN

View File

@ -23,17 +23,18 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
Returns: Returns:
argparse.Namespace: generated arguments for these test cases argparse.Namespace: generated arguments for these test cases
""" """
args.aur = True
args.changes = True
args.package = [] args.package = []
args.dependencies = True args.dependencies = True
args.dry_run = False args.dry_run = False
args.exit_code = False args.exit_code = False
args.increment = True args.increment = True
args.aur = True
args.local = True args.local = True
args.manual = True args.manual = True
args.vcs = True
args.refresh = 0 args.refresh = 0
args.username = "username" args.username = "username"
args.vcs = True
return args return args
@ -51,6 +52,7 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, configuration:
dependencies_mock = mocker.patch("ahriman.application.application.Application.with_dependencies", dependencies_mock = mocker.patch("ahriman.application.application.Application.with_dependencies",
return_value=[package_ahriman]) return_value=[package_ahriman])
updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman]) updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
changes_mock = mocker.patch("ahriman.application.application.Application.changes")
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start") on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
print_mock = mocker.patch("ahriman.application.application.Application.print_updates") print_mock = mocker.patch("ahriman.application.application.Application.print_updates")
@ -60,8 +62,9 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, configuration:
Packagers(args.username, {package_ahriman.base: "packager"}), Packagers(args.username, {package_ahriman.base: "packager"}),
bump_pkgrel=args.increment) bump_pkgrel=args.increment)
updates_mock.assert_called_once_with(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs) updates_mock.assert_called_once_with(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs)
changes_mock.assert_not_called()
dependencies_mock.assert_called_once_with([package_ahriman], process_dependencies=args.dependencies) dependencies_mock.assert_called_once_with([package_ahriman], process_dependencies=args.dependencies)
check_mock.assert_has_calls([MockCall(False, False), MockCall(False, False)]) check_mock.assert_called_once_with(False, False)
on_start_mock.assert_called_once_with() on_start_mock.assert_called_once_with()
print_mock.assert_called_once_with([package_ahriman], log_fn=pytest.helpers.anyvar(int)) print_mock.assert_called_once_with([package_ahriman], log_fn=pytest.helpers.anyvar(int))
@ -95,15 +98,17 @@ def test_run_update_empty_exception(args: argparse.Namespace, package_ahriman: P
mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman]) mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
mocker.patch("ahriman.application.application.Application.with_dependencies", return_value=[package_ahriman]) mocker.patch("ahriman.application.application.Application.with_dependencies", return_value=[package_ahriman])
mocker.patch("ahriman.application.application.Application.print_updates") mocker.patch("ahriman.application.application.Application.print_updates")
changes_mock = mocker.patch("ahriman.application.application.Application.changes")
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty") check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
_, repository_id = configuration.check_loaded() _, repository_id = configuration.check_loaded()
Update.run(args, repository_id, configuration, report=False) Update.run(args, repository_id, configuration, report=False)
check_mock.assert_has_calls([MockCall(True, False), MockCall(True, True)]) changes_mock.assert_not_called()
check_mock.assert_called_once_with(True, True)
def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, repository: Repository, def test_run_dry_run(args: argparse.Namespace, package_ahriman: Package, configuration: Configuration,
mocker: MockerFixture) -> None: repository: Repository, mocker: MockerFixture) -> None:
""" """
must run simplified command must run simplified command
""" """
@ -112,15 +117,36 @@ def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, rep
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
application_mock = mocker.patch("ahriman.application.application.Application.update") application_mock = mocker.patch("ahriman.application.application.Application.update")
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty") check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
updates_mock = mocker.patch("ahriman.application.application.Application.updates") updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
changes_mock = mocker.patch("ahriman.application.application.Application.changes")
_, repository_id = configuration.check_loaded() _, repository_id = configuration.check_loaded()
Update.run(args, repository_id, configuration, report=False) Update.run(args, repository_id, configuration, report=False)
updates_mock.assert_called_once_with(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs) updates_mock.assert_called_once_with(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs)
application_mock.assert_not_called() application_mock.assert_not_called()
changes_mock.assert_called_once_with([package_ahriman])
check_mock.assert_called_once_with(False, pytest.helpers.anyvar(int)) check_mock.assert_called_once_with(False, pytest.helpers.anyvar(int))
def test_run_no_changes(args: argparse.Namespace, configuration: Configuration, repository: Repository,
mocker: MockerFixture) -> None:
"""
must skip changes calculation
"""
args = _default_args(args)
args.dry_run = True
args.changes = False
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
mocker.patch("ahriman.application.application.Application.update")
mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
mocker.patch("ahriman.application.application.Application.updates")
changes_mock = mocker.patch("ahriman.application.application.Application.changes")
_, repository_id = configuration.check_loaded()
Update.run(args, repository_id, configuration, report=False)
changes_mock.assert_not_called()
def test_log_fn(application: Application, mocker: MockerFixture) -> None: def test_log_fn(application: Application, mocker: MockerFixture) -> None:
""" """
must print package updates must print package updates

View File

@ -270,6 +270,34 @@ def test_subparsers_package_add_option_variable_multiple(parser: argparse.Argume
assert args.variable == ["var1", "var2"] assert args.variable == ["var1", "var2"]
def test_subparsers_package_changes(parser: argparse.ArgumentParser) -> None:
"""
package-changes command must imply action, lock, quiet, report and unsafe
"""
args = parser.parse_args(["-a", "x86_64", "-r", "repo", "package-changes", "ahriman"])
assert args.action == Action.List
assert args.architecture == "x86_64"
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == "repo"
assert args.unsafe
def test_subparsers_package_changes_remove(parser: argparse.ArgumentParser) -> None:
"""
package-changes-remove command must imply action, lock, quiet, report and unsafe
"""
args = parser.parse_args(["-a", "x86_64", "-r", "repo", "package-changes-remove", "ahriman"])
assert args.action == Action.Remove
assert args.architecture == "x86_64"
assert args.lock is None
assert args.quiet
assert not args.report
assert args.repository == "repo"
assert args.unsafe
def test_subparsers_package_remove_option_architecture(parser: argparse.ArgumentParser) -> None: def test_subparsers_package_remove_option_architecture(parser: argparse.ArgumentParser) -> None:
""" """
package-remove command must correctly parse architecture list package-remove command must correctly parse architecture list
@ -633,10 +661,9 @@ def test_subparsers_repo_create_mirrorlist_option_repository(parser: argparse.Ar
def test_subparsers_repo_daemon(parser: argparse.ArgumentParser) -> None: def test_subparsers_repo_daemon(parser: argparse.ArgumentParser) -> None:
""" """
repo-daemon command must imply dry run, exit code and package repo-daemon command must imply exit code and package
""" """
args = parser.parse_args(["repo-daemon"]) args = parser.parse_args(["repo-daemon"])
assert not args.dry_run
assert not args.exit_code assert not args.exit_code
assert args.package == [] assert args.package == []

View File

@ -5,6 +5,7 @@ from pytest_mock import MockerFixture
from unittest.mock import call as MockCall from unittest.mock import call as MockCall
from ahriman.core.build_tools.sources import Sources from ahriman.core.build_tools.sources import Sources
from ahriman.core.exceptions import CalledProcessError
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
@ -12,6 +13,32 @@ from ahriman.models.remote_source import RemoteSource
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
def test_changes(mocker: MockerFixture) -> None:
"""
must calculate changes from the last known commit
"""
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch_until")
diff_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.diff", return_value="diff")
local = Path("local")
last_commit_sha = "sha"
assert Sources.changes(local, last_commit_sha) == "diff"
fetch_mock.assert_called_once_with(local, commit_sha=last_commit_sha)
diff_mock.assert_called_once_with(local, last_commit_sha)
def test_changes_skip(package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must return none in case if commit sha is not available
"""
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch_until")
diff_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.diff")
assert Sources.changes(Path("local"), None) is None
fetch_mock.assert_not_called()
diff_mock.assert_not_called()
def test_extend_architectures(mocker: MockerFixture) -> None: def test_extend_architectures(mocker: MockerFixture) -> None:
""" """
must update available architecture list must update available architecture list
@ -38,9 +65,12 @@ def test_fetch_empty(remote_source: RemoteSource, mocker: MockerFixture) -> None
""" """
mocker.patch("pathlib.Path.is_dir", return_value=True) mocker.patch("pathlib.Path.is_dir", return_value=True)
mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=False) mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=False)
head_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.head", return_value="sha")
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output")
Sources.fetch(Path("local"), remote_source) local = Path("local")
assert Sources.fetch(local, remote_source) == "sha"
head_mock.assert_called_once_with(local)
check_output_mock.assert_not_called() check_output_mock.assert_not_called()
@ -51,18 +81,20 @@ def test_fetch_existing(remote_source: RemoteSource, mocker: MockerFixture) -> N
mocker.patch("pathlib.Path.is_dir", return_value=True) mocker.patch("pathlib.Path.is_dir", return_value=True)
mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=True) mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=True)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output")
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch_until")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move") move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move")
head_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.head", return_value="sha")
local = Path("local") local = Path("local")
Sources.fetch(local, remote_source) assert Sources.fetch(local, remote_source) == "sha"
fetch_mock.assert_called_once_with(local, branch=remote_source.branch)
check_output_mock.assert_has_calls([ check_output_mock.assert_has_calls([
MockCall("git", "fetch", "--quiet", "--depth", "1", "origin",
remote_source.branch, cwd=local, logger=pytest.helpers.anyvar(int)),
MockCall("git", "checkout", "--force", remote_source.branch, cwd=local, logger=pytest.helpers.anyvar(int)), MockCall("git", "checkout", "--force", remote_source.branch, cwd=local, logger=pytest.helpers.anyvar(int)),
MockCall("git", "reset", "--quiet", "--hard", f"origin/{remote_source.branch}", MockCall("git", "reset", "--quiet", "--hard", f"origin/{remote_source.branch}",
cwd=local, logger=pytest.helpers.anyvar(int)), cwd=local, logger=pytest.helpers.anyvar(int)),
]) ])
move_mock.assert_called_once_with(local.resolve(), local) move_mock.assert_called_once_with(local.resolve(), local)
head_mock.assert_called_once_with(local)
def test_fetch_new(remote_source: RemoteSource, mocker: MockerFixture) -> None: def test_fetch_new(remote_source: RemoteSource, mocker: MockerFixture) -> None:
@ -72,9 +104,10 @@ def test_fetch_new(remote_source: RemoteSource, mocker: MockerFixture) -> None:
mocker.patch("pathlib.Path.is_dir", return_value=False) mocker.patch("pathlib.Path.is_dir", return_value=False)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move") move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move")
head_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.head", return_value="sha")
local = Path("local") local = Path("local")
Sources.fetch(local, remote_source) assert Sources.fetch(local, remote_source) == "sha"
check_output_mock.assert_has_calls([ check_output_mock.assert_has_calls([
MockCall("git", "clone", "--quiet", "--depth", "1", "--branch", remote_source.branch, "--single-branch", MockCall("git", "clone", "--quiet", "--depth", "1", "--branch", remote_source.branch, "--single-branch",
remote_source.git_url, str(local), cwd=local.parent, logger=pytest.helpers.anyvar(int)), remote_source.git_url, str(local), cwd=local.parent, logger=pytest.helpers.anyvar(int)),
@ -83,6 +116,7 @@ def test_fetch_new(remote_source: RemoteSource, mocker: MockerFixture) -> None:
cwd=local, logger=pytest.helpers.anyvar(int)) cwd=local, logger=pytest.helpers.anyvar(int))
]) ])
move_mock.assert_called_once_with(local.resolve(), local) move_mock.assert_called_once_with(local.resolve(), local)
head_mock.assert_called_once_with(local)
def test_fetch_new_without_remote(mocker: MockerFixture) -> None: def test_fetch_new_without_remote(mocker: MockerFixture) -> None:
@ -92,15 +126,17 @@ def test_fetch_new_without_remote(mocker: MockerFixture) -> None:
mocker.patch("pathlib.Path.is_dir", return_value=False) mocker.patch("pathlib.Path.is_dir", return_value=False)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move") move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move")
head_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.head", return_value="sha")
local = Path("local") local = Path("local")
Sources.fetch(local, RemoteSource(source=PackageSource.Archive)) assert Sources.fetch(local, RemoteSource(source=PackageSource.Archive)) == "sha"
check_output_mock.assert_has_calls([ check_output_mock.assert_has_calls([
MockCall("git", "checkout", "--force", Sources.DEFAULT_BRANCH, cwd=local, logger=pytest.helpers.anyvar(int)), MockCall("git", "checkout", "--force", Sources.DEFAULT_BRANCH, cwd=local, logger=pytest.helpers.anyvar(int)),
MockCall("git", "reset", "--quiet", "--hard", f"origin/{Sources.DEFAULT_BRANCH}", MockCall("git", "reset", "--quiet", "--hard", f"origin/{Sources.DEFAULT_BRANCH}",
cwd=local, logger=pytest.helpers.anyvar(int)) cwd=local, logger=pytest.helpers.anyvar(int))
]) ])
move_mock.assert_called_once_with(local.resolve(), local) move_mock.assert_called_once_with(local.resolve(), local)
head_mock.assert_called_once_with(local)
def test_fetch_relative(remote_source: RemoteSource, mocker: MockerFixture) -> None: def test_fetch_relative(remote_source: RemoteSource, mocker: MockerFixture) -> None:
@ -109,9 +145,12 @@ def test_fetch_relative(remote_source: RemoteSource, mocker: MockerFixture) -> N
""" """
mocker.patch("ahriman.core.build_tools.sources.check_output") mocker.patch("ahriman.core.build_tools.sources.check_output")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move") move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move")
head_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.head", return_value="sha")
Sources.fetch(Path("path"), remote_source) local = Path("local")
move_mock.assert_called_once_with(Path("path").resolve(), Path("path")) assert Sources.fetch(local, remote_source) == "sha"
move_mock.assert_called_once_with(local.resolve(), local)
head_mock.assert_called_once_with(local)
def test_has_remotes(mocker: MockerFixture) -> None: def test_has_remotes(mocker: MockerFixture) -> None:
@ -171,11 +210,11 @@ def test_load(package_ahriman: Package, repository_paths: RepositoryPaths, mocke
""" """
patch = PkgbuildPatch(None, "patch") patch = PkgbuildPatch(None, "patch")
path = Path("local") path = Path("local")
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch") fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch", return_value="sha")
patch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_apply") patch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_apply")
architectures_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.extend_architectures", return_value=[]) architectures_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.extend_architectures", return_value=[])
Sources.load(path, package_ahriman, [patch], repository_paths) assert Sources.load(path, package_ahriman, [patch], repository_paths) == "sha"
fetch_mock.assert_called_once_with(path, package_ahriman.remote) fetch_mock.assert_called_once_with(path, package_ahriman.remote)
patch_mock.assert_called_once_with(path, patch) patch_mock.assert_called_once_with(path, patch)
architectures_mock.assert_called_once_with(path, repository_paths.repository_id.architecture) architectures_mock.assert_called_once_with(path, repository_paths.repository_id.architecture)
@ -186,11 +225,11 @@ def test_load_no_patch(package_ahriman: Package, repository_paths: RepositoryPat
must load packages sources correctly without patches must load packages sources correctly without patches
""" """
mocker.patch("pathlib.Path.is_dir", return_value=False) mocker.patch("pathlib.Path.is_dir", return_value=False)
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch") mocker.patch("ahriman.core.build_tools.sources.Sources.fetch", return_value="sha")
mocker.patch("ahriman.core.build_tools.sources.Sources.extend_architectures", return_value=[]) mocker.patch("ahriman.core.build_tools.sources.Sources.extend_architectures", return_value=[])
patch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_apply") patch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_apply")
Sources.load(Path("local"), package_ahriman, [], repository_paths) assert Sources.load(Path("local"), package_ahriman, [], repository_paths) == "sha"
patch_mock.assert_not_called() patch_mock.assert_not_called()
@ -200,10 +239,10 @@ def test_load_with_cache(package_ahriman: Package, repository_paths: RepositoryP
""" """
mocker.patch("pathlib.Path.is_dir", return_value=True) mocker.patch("pathlib.Path.is_dir", return_value=True)
copytree_mock = mocker.patch("shutil.copytree") copytree_mock = mocker.patch("shutil.copytree")
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch") mocker.patch("ahriman.core.build_tools.sources.Sources.fetch", return_value="sha")
mocker.patch("ahriman.core.build_tools.sources.Sources.extend_architectures", return_value=[]) mocker.patch("ahriman.core.build_tools.sources.Sources.extend_architectures", return_value=[])
Sources.load(Path("local"), package_ahriman, [], repository_paths) assert Sources.load(Path("local"), package_ahriman, [], repository_paths) == "sha"
copytree_mock.assert_called_once() # we do not check full command here, sorry copytree_mock.assert_called_once() # we do not check full command here, sorry
@ -269,7 +308,7 @@ def test_add(sources: Sources, mocker: MockerFixture) -> None:
sources.add(local, "pattern1", "pattern2") sources.add(local, "pattern1", "pattern2")
glob_mock.assert_has_calls([MockCall("pattern1"), MockCall("pattern2")]) glob_mock.assert_has_calls([MockCall("pattern1"), MockCall("pattern2")])
check_output_mock.assert_called_once_with( check_output_mock.assert_called_once_with(
"git", "add", "1", "2", "1", "2", cwd=local, logger=pytest.helpers.anyvar(int) "git", "add", "1", "2", "1", "2", cwd=local, logger=sources.logger
) )
@ -284,7 +323,7 @@ def test_add_intent_to_add(sources: Sources, mocker: MockerFixture) -> None:
sources.add(local, "pattern1", "pattern2", intent_to_add=True) sources.add(local, "pattern1", "pattern2", intent_to_add=True)
glob_mock.assert_has_calls([MockCall("pattern1"), MockCall("pattern2")]) glob_mock.assert_has_calls([MockCall("pattern1"), MockCall("pattern2")])
check_output_mock.assert_called_once_with( check_output_mock.assert_called_once_with(
"git", "add", "--intent-to-add", "1", "2", "1", "2", cwd=local, logger=pytest.helpers.anyvar(int) "git", "add", "--intent-to-add", "1", "2", "1", "2", cwd=local, logger=sources.logger
) )
@ -312,7 +351,7 @@ def test_commit(sources: Sources, mocker: MockerFixture) -> None:
assert sources.commit(local, message=message) assert sources.commit(local, message=message)
check_output_mock.assert_called_once_with( check_output_mock.assert_called_once_with(
"git", "commit", "--quiet", "--message", message, "git", "commit", "--quiet", "--message", message,
cwd=local, logger=pytest.helpers.anyvar(int), environment={ cwd=local, logger=sources.logger, environment={
"GIT_AUTHOR_NAME": user, "GIT_AUTHOR_NAME": user,
"GIT_AUTHOR_EMAIL": email, "GIT_AUTHOR_EMAIL": email,
"GIT_COMMITTER_NAME": user, "GIT_COMMITTER_NAME": user,
@ -345,7 +384,7 @@ def test_commit_author(sources: Sources, mocker: MockerFixture) -> None:
assert sources.commit(Path("local"), message=message, commit_author=author) assert sources.commit(Path("local"), message=message, commit_author=author)
check_output_mock.assert_called_once_with( check_output_mock.assert_called_once_with(
"git", "commit", "--quiet", "--message", message, "git", "commit", "--quiet", "--message", message,
cwd=local, logger=pytest.helpers.anyvar(int), environment={ cwd=local, logger=sources.logger, environment={
"GIT_AUTHOR_NAME": user, "GIT_AUTHOR_NAME": user,
"GIT_AUTHOR_EMAIL": email, "GIT_AUTHOR_EMAIL": email,
"GIT_COMMITTER_NAME": user, "GIT_COMMITTER_NAME": user,
@ -366,7 +405,7 @@ def test_commit_autogenerated_message(sources: Sources, mocker: MockerFixture) -
user, email = sources.DEFAULT_COMMIT_AUTHOR user, email = sources.DEFAULT_COMMIT_AUTHOR
check_output_mock.assert_called_once_with( check_output_mock.assert_called_once_with(
"git", "commit", "--quiet", "--message", pytest.helpers.anyvar(str, strict=True), "git", "commit", "--quiet", "--message", pytest.helpers.anyvar(str, strict=True),
cwd=local, logger=pytest.helpers.anyvar(int), environment={ cwd=local, logger=sources.logger, environment={
"GIT_AUTHOR_NAME": user, "GIT_AUTHOR_NAME": user,
"GIT_AUTHOR_EMAIL": email, "GIT_AUTHOR_EMAIL": email,
"GIT_COMMITTER_NAME": user, "GIT_COMMITTER_NAME": user,
@ -383,7 +422,67 @@ def test_diff(sources: Sources, mocker: MockerFixture) -> None:
local = Path("local") local = Path("local")
assert sources.diff(local) assert sources.diff(local)
check_output_mock.assert_called_once_with("git", "diff", cwd=local, logger=pytest.helpers.anyvar(int)) check_output_mock.assert_called_once_with("git", "diff", cwd=local, logger=sources.logger)
def test_diff_specific(sources: Sources, mocker: MockerFixture) -> None:
"""
must calculate diff from specific ref
"""
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output")
local = Path("local")
assert sources.diff(local, "hash")
check_output_mock.assert_called_once_with("git", "diff", "hash", cwd=local, logger=sources.logger)
def test_fetch_until(sources: Sources, mocker: MockerFixture) -> None:
"""
must fetch until the specified commit
"""
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output", side_effect=[
"",
CalledProcessError(1, ["command"], "error"),
"",
"",
])
local = Path("local")
sources.fetch_until(local, branch="master", commit_sha="sha")
check_output_mock.assert_has_calls([
MockCall("git", "fetch", "--quiet", "--depth", "1", "origin", "master", cwd=local, logger=sources.logger),
MockCall("git", "cat-file", "-e", "sha", cwd=local, logger=sources.logger),
MockCall("git", "fetch", "--quiet", "--depth", "2", "origin", "master", cwd=local, logger=sources.logger),
MockCall("git", "cat-file", "-e", "sha", cwd=local, logger=sources.logger),
])
def test_fetch_until_first(sources: Sources, mocker: MockerFixture) -> None:
"""
must fetch first commit only
"""
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output")
local = Path("local")
sources.fetch_until(local, branch="master")
check_output_mock.assert_has_calls([
MockCall("git", "fetch", "--quiet", "--depth", "1", "origin", "master", cwd=local, logger=sources.logger),
MockCall("git", "cat-file", "-e", "HEAD", cwd=local, logger=sources.logger),
])
def test_fetch_until_all_branches(sources: Sources, mocker: MockerFixture) -> None:
"""
must fetch all branches
"""
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output")
local = Path("local")
sources.fetch_until(local)
check_output_mock.assert_has_calls([
MockCall("git", "fetch", "--quiet", "--depth", "1", cwd=local, logger=sources.logger),
MockCall("git", "cat-file", "-e", "HEAD", cwd=local, logger=sources.logger),
])
def test_has_changes(sources: Sources, mocker: MockerFixture) -> None: def test_has_changes(sources: Sources, mocker: MockerFixture) -> None:
@ -395,12 +494,34 @@ def test_has_changes(sources: Sources, mocker: MockerFixture) -> None:
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output", return_value="M a.txt") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output", return_value="M a.txt")
assert sources.has_changes(local) assert sources.has_changes(local)
check_output_mock.assert_called_once_with("git", "diff", "--cached", "--name-only", check_output_mock.assert_called_once_with("git", "diff", "--cached", "--name-only",
cwd=local, logger=pytest.helpers.anyvar(int)) cwd=local, logger=sources.logger)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output", return_value="") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output", return_value="")
assert not sources.has_changes(local) assert not sources.has_changes(local)
check_output_mock.assert_called_once_with("git", "diff", "--cached", "--name-only", check_output_mock.assert_called_once_with("git", "diff", "--cached", "--name-only",
cwd=local, logger=pytest.helpers.anyvar(int)) cwd=local, logger=sources.logger)
def test_head(sources: Sources, mocker: MockerFixture) -> None:
"""
must correctly define HEAD hash
"""
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output", return_value="sha")
local = Path("local")
assert sources.head(local) == "sha"
check_output_mock.assert_called_once_with("git", "rev-parse", "HEAD", cwd=local)
def test_head_specific(sources: Sources, mocker: MockerFixture) -> None:
"""
must correctly define ref hash
"""
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.check_output", return_value="sha")
local = Path("local")
assert sources.head(local, "master") == "sha"
check_output_mock.assert_called_once_with("git", "rev-parse", "master", cwd=local)
def test_move(sources: Sources, mocker: MockerFixture) -> None: def test_move(sources: Sources, mocker: MockerFixture) -> None:
@ -434,7 +555,7 @@ def test_patch_apply(sources: Sources, mocker: MockerFixture) -> None:
sources.patch_apply(local, patch) sources.patch_apply(local, patch)
check_output_mock.assert_called_once_with( check_output_mock.assert_called_once_with(
"git", "apply", "--ignore-space-change", "--ignore-whitespace", "git", "apply", "--ignore-space-change", "--ignore-whitespace",
cwd=local, input_data=patch.value, logger=pytest.helpers.anyvar(int) cwd=local, input_data=patch.value, logger=sources.logger
) )

View File

@ -19,9 +19,9 @@ def test_init(task_ahriman: Task, database: SQLite, mocker: MockerFixture) -> No
must copy tree instead of fetch must copy tree instead of fetch
""" """
mocker.patch("ahriman.models.package.Package.from_build", return_value=task_ahriman.package) mocker.patch("ahriman.models.package.Package.from_build", return_value=task_ahriman.package)
load_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.load") load_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.load", return_value="sha")
task_ahriman.init(Path("ahriman"), database, None) assert task_ahriman.init(Path("ahriman"), database, None) == "sha"
load_mock.assert_called_once_with(Path("ahriman"), task_ahriman.package, [], task_ahriman.paths) load_mock.assert_called_once_with(Path("ahriman"), task_ahriman.package, [], task_ahriman.paths)
@ -30,11 +30,11 @@ def test_init_bump_pkgrel(task_ahriman: Task, database: SQLite, mocker: MockerFi
must bump pkgrel if it is same as provided must bump pkgrel if it is same as provided
""" """
mocker.patch("ahriman.models.package.Package.from_build", return_value=task_ahriman.package) mocker.patch("ahriman.models.package.Package.from_build", return_value=task_ahriman.package)
mocker.patch("ahriman.core.build_tools.sources.Sources.load") mocker.patch("ahriman.core.build_tools.sources.Sources.load", return_value="sha")
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write") write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
local = Path("ahriman") local = Path("ahriman")
task_ahriman.init(local, database, task_ahriman.package.version) assert task_ahriman.init(local, database, task_ahriman.package.version) == "sha"
write_mock.assert_called_once_with(local / "PKGBUILD") write_mock.assert_called_once_with(local / "PKGBUILD")
@ -43,8 +43,8 @@ def test_init_bump_pkgrel_skip(task_ahriman: Task, database: SQLite, mocker: Moc
must keep pkgrel if version is different from provided must keep pkgrel if version is different from provided
""" """
mocker.patch("ahriman.models.package.Package.from_build", return_value=task_ahriman.package) mocker.patch("ahriman.models.package.Package.from_build", return_value=task_ahriman.package)
mocker.patch("ahriman.core.build_tools.sources.Sources.load") mocker.patch("ahriman.core.build_tools.sources.Sources.load", return_value="sha")
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write") write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
task_ahriman.init(Path("ahriman"), database, f"2:{task_ahriman.package.version}") assert task_ahriman.init(Path("ahriman"), database, f"2:{task_ahriman.package.version}") == "sha"
write_mock.assert_not_called() write_mock.assert_not_called()

View File

@ -0,0 +1,8 @@
from ahriman.core.database.migrations.m012_last_commit_sha import steps
def test_migration_last_commit_sha() -> None:
"""
migration must not be empty
"""
assert steps

View File

@ -0,0 +1,65 @@
from ahriman.core.database import SQLite
from ahriman.models.changes import Changes
from ahriman.models.package import Package
from ahriman.models.repository_id import RepositoryId
def test_changes_insert_get(database: SQLite, package_ahriman: Package) -> None:
"""
must insert and get changes
"""
database.changes_insert(package_ahriman.base, Changes("sha1", "change1"))
assert database.changes_get(package_ahriman.base).changes == "change1"
database.changes_insert(package_ahriman.base, Changes("sha2", "change2"),
RepositoryId("i686", database._repository_id.name))
assert database.changes_get(package_ahriman.base).changes == "change1"
assert database.changes_get(
package_ahriman.base, RepositoryId("i686", database._repository_id.name)).changes == "change2"
def test_changes_insert_remove(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None:
"""
must remove changes for the package
"""
database.changes_insert(package_ahriman.base, Changes("sha1", "change1"))
database.changes_insert(package_python_schedule.base, Changes("sha3", "change3"))
database.changes_insert(package_ahriman.base, Changes("sha2", "change2"),
RepositoryId("i686", database._repository_id.name))
database.changes_remove(package_ahriman.base)
assert database.changes_get(package_ahriman.base).changes is None
assert database.changes_get(package_python_schedule.base).changes == "change3"
# insert null
database.changes_insert(package_ahriman.base, Changes(), RepositoryId("i686", database._repository_id.name))
assert database.changes_get(
package_ahriman.base, RepositoryId("i686", database._repository_id.name)).changes is None
assert database.changes_get(package_python_schedule.base).changes == "change3"
def test_changes_insert_remove_full(database: SQLite, package_ahriman: Package,
package_python_schedule: Package) -> None:
"""
must remove all changes for the repository
"""
database.changes_insert(package_ahriman.base, Changes("sha1", "change1"))
database.changes_insert(package_python_schedule.base, Changes("sha3", "change3"))
database.changes_insert(package_ahriman.base, Changes("sha2", "change2"),
RepositoryId("i686", database._repository_id.name))
database.changes_remove(None)
assert database.changes_get(package_ahriman.base).changes is None
assert database.changes_get(package_python_schedule.base).changes is None
assert database.changes_get(
package_ahriman.base, RepositoryId("i686", database._repository_id.name)).changes == "change2"
def test_hashes_get(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None:
"""
must return non-empty hashes for packages
"""
database.changes_insert(package_ahriman.base, Changes("sha1", "change1"))
database.changes_insert(package_python_schedule.base, Changes())
assert database.hashes_get() == {package_ahriman.base: "sha1"}

View File

@ -199,7 +199,7 @@ def test_remote_update_get(database: SQLite, package_ahriman: Package) -> None:
""" """
must insert and retrieve package remote must insert and retrieve package remote
""" """
database.remote_update(package_ahriman) database.package_base_update(package_ahriman)
assert database.remotes_get()[package_ahriman.base] == package_ahriman.remote assert database.remotes_get()[package_ahriman.base] == package_ahriman.remote
@ -207,9 +207,9 @@ def test_remote_update_update(database: SQLite, package_ahriman: Package) -> Non
""" """
must perform package remote update for existing package must perform package remote update for existing package
""" """
database.remote_update(package_ahriman) database.package_base_update(package_ahriman)
remote_source = RemoteSource(source=PackageSource.Repository) remote_source = RemoteSource(source=PackageSource.Repository)
package_ahriman.remote = remote_source package_ahriman.remote = remote_source
database.remote_update(package_ahriman) database.package_base_update(package_ahriman)
assert database.remotes_get()[package_ahriman.base] == remote_source assert database.remotes_get()[package_ahriman.base] == remote_source

View File

@ -2,11 +2,12 @@ import pytest
from pathlib import Path from pathlib import Path
from ahriman.core.formatters import AurPrinter, ConfigurationPrinter, ConfigurationPathsPrinter, PackagePrinter, \ from ahriman.core.formatters import AurPrinter, ChangesPrinter, ConfigurationPrinter, ConfigurationPathsPrinter, \
PatchPrinter, RepositoryPrinter, StatusPrinter, StringPrinter, TreePrinter, UpdatePrinter, UserPrinter, \ PackagePrinter, PatchPrinter, RepositoryPrinter, StatusPrinter, StringPrinter, TreePrinter, UpdatePrinter, \
ValidationPrinter, VersionPrinter UserPrinter, ValidationPrinter, VersionPrinter
from ahriman.models.aur_package import AURPackage from ahriman.models.aur_package import AURPackage
from ahriman.models.build_status import BuildStatus from ahriman.models.build_status import BuildStatus
from ahriman.models.changes import Changes
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId
@ -27,6 +28,17 @@ def aur_package_ahriman_printer(aur_package_ahriman: AURPackage) -> AurPrinter:
return AurPrinter(aur_package_ahriman) return AurPrinter(aur_package_ahriman)
@pytest.fixture
def changes_printer() -> ChangesPrinter:
"""
fixture for changes printer
Returns:
ChangesPrinter: changes printer test instance
"""
return ChangesPrinter(Changes("sha", "changes"))
@pytest.fixture @pytest.fixture
def configuration_paths_printer() -> ConfigurationPathsPrinter: def configuration_paths_printer() -> ConfigurationPathsPrinter:
""" """

View File

@ -0,0 +1,32 @@
from ahriman.core.formatters import ChangesPrinter
from ahriman.models.changes import Changes
def test_properties(changes_printer: ChangesPrinter) -> None:
"""
must return non-empty properties list
"""
assert changes_printer.properties()
def test_properties_empty() -> None:
"""
must return empty properties list if change is empty
"""
assert not ChangesPrinter(Changes()).properties()
assert not ChangesPrinter(Changes("sha")).properties()
def test_title(changes_printer: ChangesPrinter) -> None:
"""
must return non-empty title
"""
assert changes_printer.title()
def test_title_empty() -> None:
"""
must return empty title if change is empty
"""
assert not ChangesPrinter(Changes()).title()
assert not ChangesPrinter(Changes("sha")).title()

View File

@ -6,6 +6,7 @@ from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite from ahriman.core.database import SQLite
from ahriman.core.repository.cleaner import Cleaner from ahriman.core.repository.cleaner import Cleaner
from ahriman.core.repository.executor import Executor from ahriman.core.repository.executor import Executor
from ahriman.core.repository.package_info import PackageInfo
from ahriman.core.repository.update_handler import UpdateHandler from ahriman.core.repository.update_handler import UpdateHandler
from ahriman.models.pacman_synchronization import PacmanSynchronization from ahriman.models.pacman_synchronization import PacmanSynchronization
@ -49,6 +50,23 @@ def executor(configuration: Configuration, database: SQLite, mocker: MockerFixtu
refresh_pacman_database=PacmanSynchronization.Disabled) refresh_pacman_database=PacmanSynchronization.Disabled)
@pytest.fixture
def package_info(configuration: Configuration, database: SQLite) -> PackageInfo:
"""
fixture for package info
Args:
configuration(Configuration): configuration fixture
database(SQLite): database fixture
Returns:
PackageInfo: package info test instance
"""
_, repository_id = configuration.check_loaded()
return PackageInfo(repository_id, configuration, database, report=False,
refresh_pacman_database=PacmanSynchronization.Disabled)
@pytest.fixture @pytest.fixture
def update_handler(configuration: Configuration, database: SQLite, mocker: MockerFixture) -> UpdateHandler: def update_handler(configuration: Configuration, database: SQLite, mocker: MockerFixture) -> UpdateHandler:
""" """

View File

@ -5,36 +5,22 @@ from pytest_mock import MockerFixture
from unittest.mock import call as MockCall from unittest.mock import call as MockCall
from ahriman.core.repository.executor import Executor from ahriman.core.repository.executor import Executor
from ahriman.models.changes import Changes
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.packagers import Packagers from ahriman.models.packagers import Packagers
from ahriman.models.user import User from ahriman.models.user import User
def test_load_archives(executor: Executor) -> None:
"""
must raise NotImplemented for missing load_archives method
"""
with pytest.raises(NotImplementedError):
executor.load_archives([])
def test_packages(executor: Executor) -> None:
"""
must raise NotImplemented for missing method
"""
with pytest.raises(NotImplementedError):
executor.packages()
def test_process_build(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None: def test_process_build(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
""" """
must run build process must run build process
""" """
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman]) mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)]) mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)])
init_mock = mocker.patch("ahriman.core.build_tools.task.Task.init") init_mock = mocker.patch("ahriman.core.build_tools.task.Task.init", return_value="sha")
move_mock = mocker.patch("shutil.move") move_mock = mocker.patch("shutil.move")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_building") status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_building")
commit_sha_mock = mocker.patch("ahriman.core.status.client.Client.package_changes_set")
executor.process_build([package_ahriman], Packagers("packager"), bump_pkgrel=False) executor.process_build([package_ahriman], Packagers("packager"), bump_pkgrel=False)
init_mock.assert_called_once_with(pytest.helpers.anyvar(int), pytest.helpers.anyvar(int), None) init_mock.assert_called_once_with(pytest.helpers.anyvar(int), pytest.helpers.anyvar(int), None)
@ -42,6 +28,7 @@ def test_process_build(executor: Executor, package_ahriman: Package, mocker: Moc
move_mock.assert_called_once_with(Path(package_ahriman.base), executor.paths.packages / package_ahriman.base) move_mock.assert_called_once_with(Path(package_ahriman.base), executor.paths.packages / package_ahriman.base)
# must update status # must update status
status_client_mock.assert_called_once_with(package_ahriman.base) status_client_mock.assert_called_once_with(package_ahriman.base)
commit_sha_mock.assert_called_once_with(package_ahriman.base, Changes("sha"))
def test_process_build_bump_pkgrel(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None: def test_process_build_bump_pkgrel(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
@ -52,6 +39,7 @@ def test_process_build_bump_pkgrel(executor: Executor, package_ahriman: Package,
mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)]) mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)])
mocker.patch("shutil.move") mocker.patch("shutil.move")
mocker.patch("ahriman.core.status.client.Client.set_building") mocker.patch("ahriman.core.status.client.Client.set_building")
mocker.patch("ahriman.core.status.client.Client.package_changes_set")
init_mock = mocker.patch("ahriman.core.build_tools.task.Task.init") init_mock = mocker.patch("ahriman.core.build_tools.task.Task.init")
executor.process_build([package_ahriman], Packagers("packager"), bump_pkgrel=True) executor.process_build([package_ahriman], Packagers("packager"), bump_pkgrel=True)
@ -85,6 +73,7 @@ def test_process_remove_base(executor: Executor, package_ahriman: Package, mocke
build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_clear") build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_clear")
patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_remove") patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_remove")
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_remove") logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_remove")
commit_sha_mock = mocker.patch("ahriman.core.database.SQLite.changes_remove")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.package_remove") status_client_mock = mocker.patch("ahriman.core.status.client.Client.package_remove")
executor.process_remove([package_ahriman.base]) executor.process_remove([package_ahriman.base])
@ -97,6 +86,7 @@ def test_process_remove_base(executor: Executor, package_ahriman: Package, mocke
patches_mock.assert_called_once_with(package_ahriman.base, []) patches_mock.assert_called_once_with(package_ahriman.base, [])
logs_mock.assert_called_once_with(package_ahriman.base, None) logs_mock.assert_called_once_with(package_ahriman.base, None)
status_client_mock.assert_called_once_with(package_ahriman.base) status_client_mock.assert_called_once_with(package_ahriman.base)
commit_sha_mock.assert_called_once_with(package_ahriman.base)
def test_process_remove_base_multiple(executor: Executor, package_python_schedule: Package, def test_process_remove_base_multiple(executor: Executor, package_python_schedule: Package,

View File

@ -0,0 +1,136 @@
import pytest
from pathlib import Path
from pytest_mock import MockerFixture
from ahriman.core.repository.package_info import PackageInfo
from ahriman.models.changes import Changes
from ahriman.models.package import Package
def test_load_archives(package_ahriman: Package, package_python_schedule: Package,
package_info: PackageInfo, mocker: MockerFixture) -> None:
"""
must return all packages grouped by package base
"""
single_packages = [
Package(base=package_python_schedule.base,
version=package_python_schedule.version,
remote=package_python_schedule.remote,
packages={package: props})
for package, props in package_python_schedule.packages.items()
] + [package_ahriman]
mocker.patch("ahriman.models.package.Package.from_archive", side_effect=single_packages)
mocker.patch("ahriman.core.database.SQLite.remotes_get", return_value={
package_ahriman.base: package_ahriman.base
})
packages = package_info.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz"), Path("c.pkg.tar.xz")])
assert len(packages) == 2
assert {package.base for package in packages} == {package_ahriman.base, package_python_schedule.base}
archives = sum([list(package.packages.keys()) for package in packages], start=[])
assert len(archives) == 3
expected = set(package_ahriman.packages.keys())
expected.update(package_python_schedule.packages.keys())
assert set(archives) == expected
def test_load_archives_failed(package_info: PackageInfo, mocker: MockerFixture) -> None:
"""
must skip packages which cannot be loaded
"""
mocker.patch("ahriman.models.package.Package.from_archive", side_effect=Exception())
assert not package_info.load_archives([Path("a.pkg.tar.xz")])
def test_load_archives_not_package(package_info: PackageInfo) -> None:
"""
must skip not packages from iteration
"""
assert not package_info.load_archives([Path("a.tar.xz")])
def test_load_archives_different_version(package_info: PackageInfo, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must load packages with different versions choosing maximal
"""
single_packages = [
Package(base=package_python_schedule.base,
version=package_python_schedule.version,
remote=package_python_schedule.remote,
packages={package: props})
for package, props in package_python_schedule.packages.items()
]
single_packages[0].version = "0.0.1-1"
mocker.patch("ahriman.models.package.Package.from_archive", side_effect=single_packages)
packages = package_info.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz")])
assert len(packages) == 1
assert packages[0].version == package_python_schedule.version
def test_package_changes(package_info: PackageInfo, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must load package changes
"""
changes = Changes("sha", "change")
load_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.load", return_value="sha2")
changes_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.changes", return_value=changes.changes)
assert package_info.package_changes(package_ahriman, changes.last_commit_sha) == changes
load_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman, [], package_info.paths)
changes_mock.assert_called_once_with(pytest.helpers.anyvar(int), changes.last_commit_sha)
def test_package_changes_skip(package_info: PackageInfo, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must skip loading package changes if no new commits
"""
changes = Changes("sha")
mocker.patch("ahriman.core.build_tools.sources.Sources.load", return_value=changes.last_commit_sha)
changes_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.changes")
assert package_info.package_changes(package_ahriman, changes.last_commit_sha) == changes
changes_mock.assert_not_called()
def test_packages(package_info: PackageInfo, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must return repository packages
"""
mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman])
load_mock = mocker.patch("ahriman.core.repository.package_info.PackageInfo.load_archives")
package_info.packages()
# it uses filter object, so we cannot verify argument list =/
load_mock.assert_called_once_with(pytest.helpers.anyvar(int))
def test_packages_built(package_info: PackageInfo, mocker: MockerFixture) -> None:
"""
must return build packages
"""
mocker.patch("pathlib.Path.iterdir", return_value=[Path("a.tar.xz"), Path("b.pkg.tar.xz")])
assert package_info.packages_built() == [Path("b.pkg.tar.xz")]
def test_packages_depend_on(package_info: PackageInfo, package_ahriman: Package, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must filter packages by depends list
"""
mocker.patch("ahriman.core.repository.repository.Repository.packages",
return_value=[package_ahriman, package_python_schedule])
assert package_info.packages_depend_on([package_ahriman], {"python-srcinfo"}) == [package_ahriman]
def test_packages_depend_on_empty(package_info: PackageInfo, package_ahriman: Package, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must return all packages in case if no filter is provided
"""
mocker.patch("ahriman.core.repository.repository.Repository.packages",
return_value=[package_ahriman, package_python_schedule])
assert package_info.packages_depend_on([package_ahriman, package_python_schedule], None) == \
[package_ahriman, package_python_schedule]

View File

@ -1,6 +1,3 @@
import pytest
from pathlib import Path
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from unittest.mock import call as MockCall from unittest.mock import call as MockCall
@ -10,9 +7,6 @@ from ahriman.core.database import SQLite
from ahriman.core.repository import Repository from ahriman.core.repository import Repository
from ahriman.core.sign.gpg import GPG from ahriman.core.sign.gpg import GPG
from ahriman.models.context_key import ContextKey from ahriman.models.context_key import ContextKey
from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource
def test_load(configuration: Configuration, database: SQLite, mocker: MockerFixture) -> None: def test_load(configuration: Configuration, database: SQLite, mocker: MockerFixture) -> None:
@ -41,105 +35,3 @@ def test_set_context(configuration: Configuration, database: SQLite, mocker: Moc
MockCall(ContextKey("sign", GPG), instance.sign), MockCall(ContextKey("sign", GPG), instance.sign),
MockCall(ContextKey("repository", Repository), instance), MockCall(ContextKey("repository", Repository), instance),
]) ])
def test_load_archives(package_ahriman: Package, package_python_schedule: Package,
repository: Repository, mocker: MockerFixture) -> None:
"""
must return all packages grouped by package base
"""
single_packages = [
Package(base=package_python_schedule.base,
version=package_python_schedule.version,
remote=package_python_schedule.remote,
packages={package: props})
for package, props in package_python_schedule.packages.items()
] + [package_ahriman]
mocker.patch("ahriman.models.package.Package.from_archive", side_effect=single_packages)
mocker.patch("ahriman.core.database.SQLite.remotes_get", return_value={
package_ahriman.base: package_ahriman.base
})
packages = repository.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz"), Path("c.pkg.tar.xz")])
assert len(packages) == 2
assert {package.base for package in packages} == {package_ahriman.base, package_python_schedule.base}
archives = sum([list(package.packages.keys()) for package in packages], start=[])
assert len(archives) == 3
expected = set(package_ahriman.packages.keys())
expected.update(package_python_schedule.packages.keys())
assert set(archives) == expected
def test_load_archives_failed(repository: Repository, mocker: MockerFixture) -> None:
"""
must skip packages which cannot be loaded
"""
mocker.patch("ahriman.models.package.Package.from_archive", side_effect=Exception())
assert not repository.load_archives([Path("a.pkg.tar.xz")])
def test_load_archives_not_package(repository: Repository) -> None:
"""
must skip not packages from iteration
"""
assert not repository.load_archives([Path("a.tar.xz")])
def test_load_archives_different_version(repository: Repository, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must load packages with different versions choosing maximal
"""
single_packages = [
Package(base=package_python_schedule.base,
version=package_python_schedule.version,
remote=package_python_schedule.remote,
packages={package: props})
for package, props in package_python_schedule.packages.items()
]
single_packages[0].version = "0.0.1-1"
mocker.patch("ahriman.models.package.Package.from_archive", side_effect=single_packages)
packages = repository.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz")])
assert len(packages) == 1
assert packages[0].version == package_python_schedule.version
def test_packages(repository: Repository, mocker: MockerFixture) -> None:
"""
must return repository packages
"""
load_mock = mocker.patch("ahriman.core.repository.repository.Repository.load_archives")
repository.packages()
# it uses filter object, so we cannot verify argument list =/
load_mock.assert_called_once_with(pytest.helpers.anyvar(int))
def test_packages_built(repository: Repository, mocker: MockerFixture) -> None:
"""
must return build packages
"""
mocker.patch("pathlib.Path.iterdir", return_value=[Path("a.tar.xz"), Path("b.pkg.tar.xz")])
assert repository.packages_built() == [Path("b.pkg.tar.xz")]
def test_packages_depend_on(repository: Repository, package_ahriman: Package, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must filter packages by depends list
"""
mocker.patch("ahriman.core.repository.repository.Repository.packages",
return_value=[package_ahriman, package_python_schedule])
assert repository.packages_depend_on([package_ahriman], {"python-srcinfo"}) == [package_ahriman]
def test_packages_depend_on_empty(repository: Repository, package_ahriman: Package, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must return all packages in case if no filter is provided
"""
mocker.patch("ahriman.core.repository.repository.Repository.packages",
return_value=[package_ahriman, package_python_schedule])
assert repository.packages_depend_on([package_ahriman, package_python_schedule], None) ==\
[package_ahriman, package_python_schedule]

View File

@ -11,14 +11,6 @@ from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource from ahriman.models.remote_source import RemoteSource
def test_packages(update_handler: UpdateHandler) -> None:
"""
must raise NotImplemented for missing method
"""
with pytest.raises(NotImplementedError):
update_handler.packages()
def test_updates_aur(update_handler: UpdateHandler, package_ahriman: Package, def test_updates_aur(update_handler: UpdateHandler, package_ahriman: Package,
mocker: MockerFixture) -> None: mocker: MockerFixture) -> None:
""" """

View File

@ -109,8 +109,7 @@ def test_key_export(gpg: GPG, mocker: MockerFixture) -> None:
""" """
check_output_mock = mocker.patch("ahriman.core.sign.gpg.check_output", return_value="key") check_output_mock = mocker.patch("ahriman.core.sign.gpg.check_output", return_value="key")
assert gpg.key_export("k") == "key" assert gpg.key_export("k") == "key"
check_output_mock.assert_called_once_with("gpg", "--armor", "--no-emit-version", "--export", "k", check_output_mock.assert_called_once_with("gpg", "--armor", "--no-emit-version", "--export", "k", logger=gpg.logger)
logger=pytest.helpers.anyvar(int))
def test_key_fingerprint(gpg: GPG, mocker: MockerFixture) -> None: def test_key_fingerprint(gpg: GPG, mocker: MockerFixture) -> None:
@ -126,8 +125,7 @@ fpr:::::::::43A663569A07EE1E4ECC55CC7E3A4240CE3C45C2:""")
key = "0xCE3C45C2" key = "0xCE3C45C2"
assert gpg.key_fingerprint(key) == "C6EBB9222C3C8078631A0DE4BD2AC8C5E989490C" assert gpg.key_fingerprint(key) == "C6EBB9222C3C8078631A0DE4BD2AC8C5E989490C"
check_output_mock.assert_called_once_with("gpg", "--with-colons", "--fingerprint", key, check_output_mock.assert_called_once_with("gpg", "--with-colons", "--fingerprint", key, logger=gpg.logger)
logger=pytest.helpers.anyvar(int))
def test_key_import(gpg: GPG, mocker: MockerFixture) -> None: def test_key_import(gpg: GPG, mocker: MockerFixture) -> None:
@ -138,7 +136,7 @@ def test_key_import(gpg: GPG, mocker: MockerFixture) -> None:
check_output_mock = mocker.patch("ahriman.core.sign.gpg.check_output") check_output_mock = mocker.patch("ahriman.core.sign.gpg.check_output")
gpg.key_import("keyserver.ubuntu.com", "0xE989490C") gpg.key_import("keyserver.ubuntu.com", "0xE989490C")
check_output_mock.assert_called_once_with("gpg", "--import", input_data="key", logger=pytest.helpers.anyvar(int)) check_output_mock.assert_called_once_with("gpg", "--import", input_data="key", logger=gpg.logger)
def test_process(gpg_with_key: GPG, mocker: MockerFixture) -> None: def test_process(gpg_with_key: GPG, mocker: MockerFixture) -> None:

View File

@ -6,6 +6,7 @@ from ahriman.core.configuration import Configuration
from ahriman.core.status.client import Client from ahriman.core.status.client import Client
from ahriman.core.status.web_client import WebClient from ahriman.core.status.web_client import WebClient
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.changes import Changes
from ahriman.models.internal_status import InternalStatus from ahriman.models.internal_status import InternalStatus
from ahriman.models.log_record_id import LogRecordId from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package from ahriman.models.package import Package
@ -87,6 +88,20 @@ def test_package_add(client: Client, package_ahriman: Package) -> None:
client.package_add(package_ahriman, BuildStatusEnum.Unknown) client.package_add(package_ahriman, BuildStatusEnum.Unknown)
def test_package_changes_get(client: Client, package_ahriman: Package) -> None:
"""
must return null changes
"""
assert client.package_changes_get(package_ahriman.base) == Changes()
def test_package_changes_set(client: Client, package_ahriman: Package) -> None:
"""
must process changes update without errors
"""
client.package_changes_set(package_ahriman.base, Changes())
def test_package_get(client: Client, package_ahriman: Package) -> None: def test_package_get(client: Client, package_ahriman: Package) -> None:
""" """
must return empty package list must return empty package list

View File

@ -6,6 +6,7 @@ from unittest.mock import call as MockCall
from ahriman.core.exceptions import UnknownPackageError from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.status.watcher import Watcher from ahriman.core.status.watcher import Watcher
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.changes import Changes
from ahriman.models.log_record_id import LogRecordId from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
@ -88,6 +89,25 @@ def test_logs_update_update(watcher: Watcher, package_ahriman: Package, mocker:
insert_mock.assert_called_once_with(log_record_id, 42.01, "log record", watcher.repository_id) insert_mock.assert_called_once_with(log_record_id, 42.01, "log record", watcher.repository_id)
def test_package_changes_get(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must return package changes
"""
get_mock = mocker.patch("ahriman.core.database.SQLite.changes_get", return_value=Changes("sha"))
watcher.known = {package_ahriman.base: (package_ahriman, BuildStatus())}
assert watcher.package_changes_get(package_ahriman.base) == Changes("sha")
get_mock.assert_called_once_with(package_ahriman.base, watcher.repository_id)
def test_package_changes_get_failed(watcher: Watcher, package_ahriman: Package) -> None:
"""
must raise UnknownPackageError in case of unknown package
"""
with pytest.raises(UnknownPackageError):
watcher.package_changes_get(package_ahriman.base)
def test_package_get(watcher: Watcher, package_ahriman: Package) -> None: def test_package_get(watcher: Watcher, package_ahriman: Package) -> None:
""" """
must return package status must return package status

View File

@ -8,6 +8,7 @@ from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.status.web_client import WebClient from ahriman.core.status.web_client import WebClient
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.changes import Changes
from ahriman.models.internal_status import InternalStatus from ahriman.models.internal_status import InternalStatus
from ahriman.models.log_record_id import LogRecordId from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package from ahriman.models.package import Package
@ -39,6 +40,14 @@ def test_status_url(web_client: WebClient) -> None:
assert web_client._status_url().endswith("/api/v1/status") assert web_client._status_url().endswith("/api/v1/status")
def test_changes_url(web_client: WebClient, package_ahriman: Package) -> None:
"""
must generate changes url correctly
"""
assert web_client._changes_url(package_ahriman.base).startswith(web_client.address)
assert web_client._changes_url(package_ahriman.base).endswith(f"/api/v1/packages/{package_ahriman.base}/changes")
def test_logs_url(web_client: WebClient, package_ahriman: Package) -> None: def test_logs_url(web_client: WebClient, package_ahriman: Package) -> None:
""" """
must generate logs url correctly must generate logs url correctly
@ -111,6 +120,121 @@ def test_package_add_failed_http_error_suppress(web_client: WebClient, package_a
logging_mock.assert_not_called() logging_mock.assert_not_called()
def test_package_changes_get(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must get changes
"""
changes = Changes("sha")
response_obj = requests.Response()
response_obj._content = json.dumps(changes.view()).encode("utf8")
response_obj.status_code = 200
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj)
result = web_client.package_changes_get(package_ahriman.base)
requests_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True),
params=web_client.repository_id.query())
assert result == changes
def test_package_changes_get_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must suppress any exception happened during changes fetch
"""
mocker.patch("requests.Session.request", side_effect=Exception())
web_client.package_changes_get(package_ahriman.base)
def test_package_changes_get_failed_http_error(web_client: WebClient, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must suppress HTTP exception happened during changes fetch
"""
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
web_client.package_changes_get(package_ahriman.base)
def test_package_changes_get_failed_suppress(web_client: WebClient, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must suppress any exception happened during changes fetch and don't log
"""
web_client.suppress_errors = True
mocker.patch("requests.Session.request", side_effect=Exception())
logging_mock = mocker.patch("logging.exception")
web_client.package_changes_get(package_ahriman.base)
logging_mock.assert_not_called()
def test_package_changes_get_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must suppress HTTP exception happened during changes fetch and don't log
"""
web_client.suppress_errors = True
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
logging_mock = mocker.patch("logging.exception")
web_client.package_changes_get(package_ahriman.base)
logging_mock.assert_not_called()
def test_package_changes_set(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must set changes
"""
changes = Changes("sha")
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
web_client.package_changes_set(package_ahriman.base, changes)
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
params=web_client.repository_id.query(), json=changes.view())
def test_package_changes_set_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must suppress any exception happened during changes update
"""
mocker.patch("requests.Session.request", side_effect=Exception())
web_client.package_changes_set(package_ahriman.base, Changes())
def test_package_changes_set_failed_http_error(web_client: WebClient, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must suppress HTTP exception happened during changes update
"""
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
web_client.package_changes_set(package_ahriman.base, Changes())
def test_package_changes_set_failed_suppress(web_client: WebClient, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must suppress any exception happened during changes update and don't log
"""
web_client.suppress_errors = True
mocker.patch("requests.Session.request", side_effect=Exception())
logging_mock = mocker.patch("logging.exception")
web_client.package_changes_set(package_ahriman.base, Changes())
logging_mock.assert_not_called()
def test_package_changes_set_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must suppress HTTP exception happened during changes update and don't log
"""
web_client.suppress_errors = True
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
logging_mock = mocker.patch("logging.exception")
web_client.package_changes_set(package_ahriman.base, Changes())
logging_mock.assert_not_called()
def test_package_get_all(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: def test_package_get_all(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
""" """
must return all packages status must return all packages status

View File

@ -0,0 +1,26 @@
from ahriman.models.changes import Changes
def test_is_empty() -> None:
"""
must check if changes are empty
"""
assert Changes().is_empty
assert Changes("sha").is_empty
assert not Changes("sha", "change").is_empty
assert not Changes(None, "change").is_empty # well, ok
def test_changes_from_json_view() -> None:
"""
must construct same object from json
"""
changes = Changes()
assert Changes.from_json(changes.view()) == changes
changes = Changes("sha")
assert Changes.from_json(changes.view()) == changes
changes = Changes("sha", "change")
assert Changes.from_json(changes.view()) == changes

View File

@ -0,0 +1 @@
# schema testing goes in view class tests

View File

@ -0,0 +1,84 @@
import pytest
from aiohttp.test_utils import TestClient
from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.changes import Changes
from ahriman.models.package import Package
from ahriman.models.user_access import UserAccess
from ahriman.web.views.v1.status.changes import ChangesView
async def test_get_permission() -> None:
"""
must return correct permission for the request
"""
for method in ("GET",):
request = pytest.helpers.request("", "", method)
assert await ChangesView.get_permission(request) == UserAccess.Reporter
for method in ("POST",):
request = pytest.helpers.request("", "", method)
assert await ChangesView.get_permission(request) == UserAccess.Full
def test_routes() -> None:
"""
must return correct routes
"""
assert ChangesView.ROUTES == ["/api/v1/packages/{package}/changes"]
async def test_get(client: TestClient, package_ahriman: Package) -> None:
"""
must get changes for package
"""
changes = Changes("sha", "change")
await client.post(f"/api/v1/packages/{package_ahriman.base}",
json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
await client.post(f"/api/v1/packages/{package_ahriman.base}/changes", json=changes.view())
response_schema = pytest.helpers.schema_response(ChangesView.get)
response = await client.get(f"/api/v1/packages/{package_ahriman.base}/changes")
assert response.status == 200
assert await response.json() == changes.view()
assert not response_schema.validate(changes.view())
async def test_get_not_found(client: TestClient, package_ahriman: Package) -> None:
"""
must return not found for missing package
"""
response_schema = pytest.helpers.schema_response(ChangesView.get, code=404)
response = await client.get(f"/api/v1/packages/{package_ahriman.base}/changes")
assert response.status == 404
assert not response_schema.validate(await response.json())
async def test_post(client: TestClient, package_ahriman: Package) -> None:
"""
must update package changes
"""
await client.post(f"/api/v1/packages/{package_ahriman.base}",
json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
request_schema = pytest.helpers.schema_request(ChangesView.post)
changes = Changes("sha", "change")
assert not request_schema.validate(changes.view())
response = await client.post(f"/api/v1/packages/{package_ahriman.base}/changes", json=changes.view())
assert response.status == 204
response = await client.get(f"/api/v1/packages/{package_ahriman.base}/changes")
assert await response.json() == changes.view()
async def test_post_exception(client: TestClient, package_ahriman: Package) -> None:
"""
must raise exception on invalid payload
"""
response_schema = pytest.helpers.schema_response(ChangesView.post, code=400)
response = await client.post(f"/api/v1/packages/{package_ahriman.base}/changes", json=[])
assert response.status == 400
assert not response_schema.validate(await response.json())