Skip to content

djr7C4/subtree-package

Repository files navigation

Demos

Installing STP with the bootstrap script

Installing packages

Using transient to select the auto controller

With the interactive controller

Checking the latest versions of packages

Upgrading packages

Go to installation if you’re ready to install STP or check out the comparison with other package managers!

Motivation

Anyone with a sufficiently complex Emacs configuration knows that upgrading installed packages can break one’s configuration. Therefore, it is important to be able to restore the installed packages to a previous version. The natural way to do this is using git.

One approach is to simply use the built-in package.el but make ~/.emacs.d/elpa into a git repository. This works but sometimes does not allow for sufficient control over the versions of the installed packages since package archives only allow installing the most recent version of a package.

The approach STP takes is to represent each package as git subtrees. This can also be done using manual git commands but several common tasks are tedious.

  1. Navigating to Github in a web browser to find the URL for each new package
  2. Determining what version to upgrade to via git subtree pull via another trip to Github
  3. Managing the dependencies of source packages

STP addresses all of these issues and much more.

  1. Package repositories are automatically determined from the package name using package archives enabled in package-archives.
  2. The version to install or upgrade to defaults to the latest stable version and can also be selected using completion when the command is run with a prefix argument. STP detects the suitable tags and branches to provide as completion candidates and shows how recent the latest stable version is.
  3. The policy for choosing new versions and many other details of the package installation process such as security audits, updating the lock file and resetting when errors occur can be toggled via a convenient transient menu.
  4. Dependencies of packages are detected automatically when a package is installed or upgraded and are automatically upgraded or installed as necessary.

Features

Easily manage packages

The main entry point to STP is M-x stp-list which shows all installed packages and has convenience bindings for

  • installing a package (i),
  • upgrading a package (u),
  • installing or upgrading a package group (U),
  • adding or editing a package group (E),
  • uninstalling a package group (D),
  • deleting a package group (x),
  • building source (b),
  • building info (m),
  • editing remotes (e),
  • uninstalling (x),
  • toggling between stable and unstable (t),
  • finding the latest stable and unstable versions for
    • the current package (v) or
    • all packages (V),
  • repairing (r) and
  • editing available remotes (e),
  • reloading (G),
  • adding to the load path (l),
  • running post actions (a) and
  • viewing the source code (RET)

Most complex commands such as stp-install-command and stp-upgrade-command allow their default options (automatically committing, pushing and so forth) to be modified via a transient menu when the command is run with a prefix argument.

Specify the level control you want over packages

By default, when you select a package to install, STP determines the details (e.g. the remote, the update policy (stable or unstable) and the version) automatically. By default, stable versions are preferred. To prefer the tip of the upstream branch instead, add the following to your ~/.emacs.d/init.el.

(require 'stp)

(setq stp-default-controller-args '(:preferred-update unstable))

To instruct STP to prompt you for package details, use a universal prefix argument with the install or upgrade command and use C from the transient menu to enable the interactive controller.

If you want STP to always prompt you by default for maximum control, add the following to your ~/.emacs.d/init.el.

(require 'stp)

(setq stp-default-controller-class 'stp-interative-controller)

It is also possible to define your own custom policy by defining a subclass of stp-controller.

Freely edit installed packages

Packages installed in stp-source-directory can be freely modified. If you later upgrade the package to a newer version, any conflicts that arise due to your changes can be resolved with git as you would for any git repository. STP automatically detects merge conflicts and leaves them to the user. This is true even when the remote for the package is not a git repository.

Automatically detect stable versions

Stable versions of packages are automatically inferred from tags in the git repository. stp-version-extractor-alist can be modified in order to support unusual version strings if a package uses a scheme that is not supported by the defaults. Contributions of missing version extractors are very welcome!

Automatically discover git repositories

STP can automatically discover the git repository of a package if it is specified by an archive configured in package-archive-contents or is on the Emacs mirror. Alternatively, the user can enter a different repository if desired.

Install any commit by branch or hash

Git repositories are automatically fetched before packages are installed or upgraded when stp-subtree-fetch is non-nil. This allows a hash to be specified instead of a tag or branch.

Automatic dependency resolution

Once a package is installed or upgraded, its dependencies are automatically determined and the required versions will be installed or upgraded. STP keeps track of which packages were installed as dependencies. When a package is uninstalled, its dependencies are also removed if they are no longer needed and were not installed manually by the user.

By default, the user is told what the minimum required version of a dependency is but is allowed to installed older versions. Set stp-enforce-min-version to t to only allow versions that satisfy the requirement.

Sometimes, the same git repository contains multiple packages that would be installed via package.el or using the archive method. An example is the helm repository which contains both the helm and helm-core packages. When installing via git only helm should be installed since helm-core corresponds to the same repository and would result in an extra copy. Newer versions of STP will automatically detect circular dependencies and other instances of duplicate packages and only install one copy. Manually skipping one of the copies with =C-c C-k once STP prompts the user to begin installing it is still allowed but isn’t necessary anymore.

Mark packages as stable or unstable

Stable releases are automatically detected from tags in git repositories or ELPA releases. Stable packages are only upgraded to other stable releases by default.

Packages can also be marked as unstable which allows them to installed and upgraded to any hash, branch or tag.

Find upgradable packages

Use v in stp-list-mode to fetch the latest stable and unstable versions for the current package and display this information in a latest field. Packages that can be upgraded are highlighted in blue. This is done asynchronously if stp-latest-version-async is non-nil.

Use V to fetch the latest stable and unstable versions for all packages with latest versions that have not been updated for at least stp-latest-versions-stale-interval. This is parallelized using stp-latest-num-processes processes asynchronously if stp-latest-version-async is non-nil. With a universal prefix argument, the meaning of stp-latest-version-async is inverted. With a negative prefix argument, all packages are updated instead of only those with stale latest versions. The updated latest versions are inserted into the STP list buffer as they become available.

Only git packages have unstable versions so this information is omitted for ELPA packages. There is no way to retrieve any version information at all for URL packages so the latest version is omitted entirely.

For git packages, the number of commits required to reach the latest version from the currently installed version is shown in parentheses followed by the amount of time between the commits for the installed version and the new version. Negative numbers mean that the currently installed version is newer by that many commits. When the two commits have a common ancestor but neither is an ancestor of the other, +<commits-ahead>-<commits-behind> will be shown.

For ELPA packages, the number in parentheses is the number of stable versions since the currently installed version. To show the date a version was committed instead of the amount of time from the installed version, set stp-annotated-version-type to 'timestamp.

Latest versions are considered stale when they haven’t been updated for more than stp-latest-versions-stale-interval seconds and are highlighted in orange.

Note that if you have safe.bareRepository set to explicit in your .gitconfig for security against malicious embedded bare repositories the commands for updating the latest versions will not work unless stp-override-bare-repository is non-nil. This allows STP to run git commands in the bare repositories that are used to cache the repositories of installed packages and should not pose a security risk.

Choose between multiple remotes

STP remembers all remotes that the user has entered when upgrading a package. These are available for the user to choose for future upgrades using completion. Set stp-development-directory to the directory that contains your elisp repositories to allow for convenient completion of local directories via stp-install, stp-upgrade and stp-edit-remotes. Simply enter ./ at the remote completion prompt to begin directory completion.

Quickly open the source in a local git repository

When developing packages that are installed with STP, a copy of the source code will be installed as a git subtree and there will also be a local copy of git repository on which development is already performed. With an installed packages copy of a file open, stp-find-package can be to find the corresponding file on local git repositories that are registered as remotes for that package or are in stp-development-directory. When desired, install or upgrade the package from the local git repository using stp-install or stp-upgrade.

Automatically build packages

By default, building occurs automatically when packages are installed or upgraded. Packages can also be built manually using b in stp-list-mode. Packages can be built automatically by detecting Makefiles or other build systems as well as compiling the elisp files directly. Info manuals are also automatically detected and added to Emacs’ info search path.

Automatically repair packages

Use r in stp-list-mode to repair information for a package in the package database that may contain errors. This can be useful when the package database is not updated because a user updated a package using git commands manually or there is a bug. R can be used to repair all packages.

Supported package types

Currently, four different types of packages can be installed. STP automatically infers the packages type based on the remote provided so it is not necessary to specify the type manually.

Git repositories

To install a git repository in STP, simply provide its URL as the remote. Some packages are also available as ELPA or URL packages. In such cases, it is usually best to install them as git packages instead.

GNU ELPA packages

Use the page for the package on elpa.gnu.org as the remote. For example, for ace-window this would be https://elpa.gnu.org/packages/ace-window.html. Unlike other archives supported by STP, older versions of ELPA packages can be chosen instead of just the current one.

Archive packages

Other archives such as melpa and melpa-stable are also supported. STP inspects package-archive-contents and supports all archives specified there. Unlike ELPA packages, only the current version can be installed.

URL packages

When the source is a single file or a tarball that can be accessed via a URL the package can be installed as a URL package using this URL as the remote. Unlike git and ELPA packages, there is no way for STP to detect the version of a URL package due to the lack of useful metadata. Therefore, it is up to the user to supply the version in this case.

Optionally sync your Emacs configuration repository to ~/.emacs.d

stp-source-directory can be a subdirectory of ~/.emacs.d or it can be a separate git repository just for packages. The main benefit of this is that it avoids cluttering the commit history in ~/.emacs.d with many entries regarding package changes. However, the disadvantage is that checking out an old version of ~/.emacs.d will just use whatever is currently in the package git repository instead of what was current when that version of ~/.emacs.d was committed.

STP supports updating a lock file for the version of the package git repository when changes are made to the packages. This will cause the version of the package git repository stored in stp-lock-file to be used. To enable this feature, put the following code in ~/.emacs.d/early-init.el. It should go before the bootstrap code mentioned below.

(require 'stp-locked (expand-file-name "path/to/package-source/subtree-package/stp-locked.el"))

(stp-checkout-locked-revision)

In your ~/.emacs.d/init.el, enable automatically updating the stp-lock-file as follows.

(setq stp-auto-lock t
      stp-never-auto-lock nil)

(file-notify-add-watch stp-lock-file '(change) #'stp-lock-file-watcher)

Manage related packages as groups

Groups of related packages can be created or edited with E in stp-list-mode. Use x to remove a group that is no longer needed. Use U to install or upgrade packages in the specified groups. The name of a package can also be specified instead of a group. D can be used to uninstall a package group.

Manage package headers

STP provides some utilities to streamline the management of elisp package headers. Use M-x stp-bump-version to change the version header of the current file and automatically add a git tag with the same name. Use M-x stp-headers-update-elisp-headers to try to infer updated values of a number of elisp headers and automatically update them. In particular, this updates the requirements to the versions that are currently installed.

Audit changes to packages

By default, changes to packages are presented to the user as diffs before the code is loaded. This helps mitigate supply chain attacks such as the recent one against the kubernetes.el but can be time consuming. Add

(setq stp-audit-changes nil)

to your configuration if you wish to disable this feature. By default, git reset is used to restore the previous state when an audit fails. stp-audit-auto-reset controls this behavior.

Modify the default behavior with custom hooks

The variables stp-pre-install-functions, stp-post-install-functions, stp-pre-upgrade-functions, stp-post-upgrade-functions, stp-pre-uninstall-functions, stp-post-uninstall-functions, stp-pre-reinstall-functions, stp-post-reinstall-functions, stp-pre-action-functions and stp-post-action-functions allow the user to run custom functions before and after STP operations.

Installation

Subtree package can manage itself as a package but first it needs to be installed along with its dependencies using other means. You can either use the bootstrap script or install use package.el.

Using the bootstrap script

The simplest way is to run the bootstrap script from this repository in the top-level directory of the git repository for your Emacs configuration.

cd <emacs-git-directory>
# Initialize the git repository and add at least one commit.
curl -s https://raw.githubusercontent.com/djr7C4/subtree-package/refs/heads/main/bootstrap > bootstrap
bash bootstrap

The script supports installing the recommended stable (the versions tested during development), latest stable or unstable versions of the packages.

STP also requires that you install the atool CLI utility if you wish to install archive, ELPA and compressed URL packages. Additionally, to support certain non-essential features, the async and queue packages are required. A few optional features require the gh CLI utility.

The bootstraping code should be placed in ~/.emacs.d/early-init.el. It is recommended to create a directory named package-source at the top-level of the git repository for your configuration.

(setq stp-source-directory "<path/to/package-source>"
      stp-info-file (expand-file-name "../stp-pkg-info.eld" stp-source-directory))

(require 'stp-locked (expand-file-name "subtree-package/stp-locked.el" stp-source-directory))
(require 'stp-bootstrap (expand-file-name "subtree-package/stp-bootstrap.el" stp-source-directory))

  ;; Set up dependencies for STP itself.
  (stp-bootstrap)
  ;; Add installed packages to the load path.
  (stp-update-load-paths)

In your ~/.emacs.d/init.el, add the following code.

(require 'stp)

;; By default, STP is highly interactive and prompts the user quite a bit for
;; maximum control. The following enables the auto controller which keeps
;; prompts to a minimum.
(setq stp-default-controller-class 'stp-auto-controller
      stp-default-controller-args '(:preferred-update stable))

(keymap-global-set "C-c P" #'stp-list)

;; Improve performance by adding various variables to
;; `savehist-additional-variables' in order to avoid repeating expensive
;; computations.
(require 'savehist)

;; The order of these calls is important.
(savehist-mode 1)
(stp-setup)

(keymap-global-set "C-c O" #'stp-find-package)
(keymap-global-set "C-c P" #'stp-list)

The first time Emacs is started with STP enabled, it will search the load path to initialize its internal cache. This will take a few moments. Once it has finished, run M-x stp-repair-all to initialize the package information database from the git repository. This will cause the packages installed by the bootstrap script to be recognized as STP packages.

Using package.el

Alternatively, install a working instance of STP using package.el.

(package-vc-install "https://github.com/djr7c4/rem.git")
(package-vc-install "https://github.com/djr7c4/subtree-package.git")
(add-to-list 'load-path (expand-file-name package-user-dir "rem"))
(add-to-list 'load-path (expand-file-name package-user-dir "subtree-package"))

Then manually install all dependencies of STP from within STP. The requirements are listed in the file bootstrap-requirements in this repository. Once they are installed, you can remove the packages installed with package.el.

Comparison with other package managers

The main strengths of STP are

  • It is highly interactive and works hard to avoid forcing you to leave Emacs during the installation process (for example to find repositories and releases on github).
  • There are many configuration options possible. By setting a few variables, you can configure STP to prompt the user for every detail or automatically resolve user prompts whenever possible or prompt according to a custom policy.
  • Your configuration is completely reproducible without depending on any external git repositories. Even if all external repositories were deleted, you would still be able to immediately reproduce your configuration simply by cloning it.

Elpaca

Elpaca is similar to STP in some ways in that both are able to install packages from their git repositories and can determine the repository URL automatically from ELPA/MELPA.

Some differences include

  • Elpaca lacks a method for interactively installing stable versions of packages as it does not support interactively choosing a git tag corresponding to a stable release.
  • Elpaca creates copies of git repositories instead of adding them to the repository as git subtrees. In STP, the subtree approach is used in order to make the configuration completely reproducible without having to clone remote repositories (for example on a new machine).

Straight.el

Straight is a primarily functional package manager and relies on code in your initialization files to determine which packages should be installed. This consists of expressions such as

;; Copied from the Straight.el README
(el-patch :type git :host github :repo "radian-software/el-patch")

It also supports interactive package installation. Like Elpaca, it lacks a method for installing stable versions of packages and clones git repositories rather than adding them as git subtrees.

Straight does not check that the requirements of installed packages have recent enough versions.

Borg

Borg is based on a similar idea to STP but uses git submodules instead of git subtrees. While a subtree is a local copy of a package’s git repository, a submodule is a reference to a package’s repository consisting of its URL and the current hash that is being used.

This has several implications

  • With subtrees, your configuration does not depend on external repositories. All you have to do to reproduce it is clone your git repository.
  • With submodules, cloning your git repository will not copy the submodules. For that, you have to supply the `–recurse-submodules` flag to `git clone` or use the `git submodule init` and `git submodule update` commands. Both methods involves cloning the external repository referenced by the submodule and if the repository moves or no longer exists, your configuration will break.
  • Subtrees allow you to easily make local changes to packages and merge new commits to the package repository using git’s standard merge machinery.
  • Since submodules are just references to external repositories, local changes are not advisable as they will be overwritten if you clone the repository and reinitialize your submodules.

package-vc

package-vc is a builtin library that can install packages from git repositories. Like STP, it allows you to select a package by name and automatically install it from the repository.

Some differences include

  • package-vc does not automatically check packages into git
  • When you run package-vc-install, the package you specify is installed from the git repository but its dependencies are installed from package archives like ELPA and MELPA instead.
  • There are some cases where STP can find the git repository and package-vc cannot. This is because Emacs package archives do not have a field for the git repository. Most authors specify the repository in the :url field but this is only a convention. STP has heuristics that allow it to infer the git repository in some cases even when it is not directly specified. Additionally, STP can find the git repositories for packages that are hosted on the Emacs mirror.
  • There is no mechanism for interactively choosing a stable version tag to install.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors