Changesets

Just as source code control systems use patch files to describe the differences between two versions of a file, Conary uses changesets to describe the differences between versions of troves and files. These changesets include information on how files have changed, as well as how the troves that reference those files have changed.

These changesets are often transient objects; they are created as part of an operation and disappear when that operation has completed. They can also be stored in files, however, which allows them to be distributed like the packages produced by a classical package management system.

Applying changesets rather than installing new versions of packages allows Conary to update only the parts of a package that have changed, rather than blindly reinstalling every file in the package.

Besides saving space and bandwidth, representing updates as changes has another advantage: it allows merging. Conary intelligently merges changes not only to file contents, but also to file metadata such as permissions.

This capability is very useful if you wish to maintain a branch or shadow of a package—for example, keeping current with vendor maintenance of a package, while adding a couple of patches to meet local needs.

Conary also keeps track of local changes in essentially the same way, preserving them. When, for example, you add a few lines to a configuration file on an installed system, and then a new version of a package is released with changes to that configuration file, Conary can merge the two unless there is a direct conflict (unusual but possible). If you change a file's permission bits, those changes will be preserved across upgrades.

Conary supports two types of change sets:

In the first case, where Conary is calculating the differences between two different versions, the result is a relative changeset. In the second case, where Conary is encoding the entire content of the version, the result is an absolute changeset. (If you use an absolute changeset to upgrade to the version provided in the absolute changeset, Conary internally converts the changeset to a relative changeset, thereby preserving your local changes.) Absolute changesets are convenient ways of distributing versions of troves and files to users who have various versions of those items already installed on their systems. In practice, they can be distributed just like package files created by traditional package management systems.

Representing Local Changes

Conary can also generate a local changeset that is a relative changeset showing the difference between the repository and the local system for the version of a trove that is installed. You can distribute a local changeset to another machine in two ways:

  • You can distribute it to other machines with the same version of the trove in question installed.

  • You can commit the local changeset to a branch of a repository, and then update to that branch on target machines.

There is an important distinction between the two cases. In the first case, the machine that applies the changeset will act as if those changes had been made by the system's administrator; since those changes are not in a repository they are not versioned. In the second case, however, the machine gets those changes by updating the trove to the branch that contains those changes, and it can continue to track changes from that branch.

For example, assume that you have machines with troves from branches labeled conary.rpath.com@rpl:rel1 installed, and you have some local changes that you want to distribute to a group of machines. Let's say that after updating to version 2.9.0-1-2 of tmpwatch, you want to change the permissions of the /usr/sbin/tmpwatch binary because you are paranoid: chmod 100 /usr/sbin/tmpwatch Now, you record that change in a local changeset; that changeset is relative to 2.9.0-1-2, and describes your local changes.

You then commit your local changeset to the conary.example.com@local:paranoid branch in your local repository. Now, on all the machines in the group, you can update tmpwatch conary.example.com@local:paranoid. Each machine will now look in the conary.example.com repository on the paranoid branch if you simply run conary update tmpwatch. This means that if you make further changes to the tmpwatch package, you can commit those changes to the paranoid branch on the conary.example.com repository, and each of the machines will update to the latest version you have committed to that branch. Every time a new version of tmpwatch is released on the conary.rpath.com@rpl:rel1 branch, you will have to apply the changeset to the conary.example.com@local:paranoid branch before the machines with your paranoid branch installed will update their copies of tmpwatch.

Figure 4. Local Changesets

Local Changesets

If rather than maintaining a branch, you merely want to distribute some changes that are local to the group of machines, you do not want to commit the local changeset to the repository. Instead, you want to copy the changeset file (let's call it paranoid.ccs) to each machine and run conary localcommit paranoid.ccs on each machine. Now, your change to permissions applies to each system, but conary update tmpwatch will still look at conary.rpath.com@rpl:rel1 and Conary will apply updates to tmpwatch from conary.rpath.com@rpl:rel1 without additional work required on your part, and it will preserve the change to the permissions of the /usr/sbin/tmpwatch binary on each machine.

Both ways of managing local change are useful. Committing local changesets to a repository is best for systems with entirely centralized management policy, where all system changes must be cleared by some central agency, whereas distributing local changesets is best when individual systems are expected to autonomously update themselves asynchronously.

Merging

When Conary updates a system, it does not blindly obliterate all changes that have been made on the local system. Instead, it does a three-way merge between the currently installed version of a a file as originally installed, that file on the local system, and the version of the file being installed. If an attribute of the file was not changed on the local system, that attribute's value is set from the new version of the package. Similarly, if the attribute did not change between versions of the package, the attribute from the local system is preserved. The only time conflicts occur is if both the new value and the local value of the attribute have changed; in that case a warning is given and the administrator needs to resolve the conflict.

For configuration files, Conary creates and applies context diffs. This preserves changes using the the widely-understood diff/patch process.

Efficiency

Conary is more efficient than traditional packaging systems in several ways.

  • By utilizing relative changesets whenever possible, Conary uses less bandwidth.

  • By modifying only changed files on updates, Conary uses less time to do updates, particularly for large packages with small changes.

  • By using a versioned repository, Conary saves space because unchanged files are stored once for the whole repository, instead of once in each version of each package.

  • By enabling distributed repositories, Conary

    • saves the time it takes to maintain a modified copy of an entire repository, and

    • saves the space it takes to store complete copies of an entire repository.

Rollbacks

Because Conary updates systems by applying changesets, and because it is able to follow changes on the local system intrinsically, it easily supports rollbacks. If requested, Conary can store an inverse changeset that represents each transaction (a set of trove updates that maintains system consistency, including any dependencies) that it commits to the local system. If the update creates or causes problems, the administrator can ask Conary to install the changeset that represents the rollback.

Because rollbacks can affect each other, they are strictly stacked; you can (in effect) go backward through time, but you cannot browse. You have to apply the most recent rollback before you apply the next most recent rollback, and so forth.

This restriction might seem like a great inconvenience, but it is not. Because Conary maintains local changes vigorously, including merging changes to configuration files, and because all the old versions you might have installed before are still in the repositories they came from, you can “update” to older versions of troves and get practically the same effect as rolling back your upgrade from that older version.

Applying rollbacks can be more convenient when you know that you want to roll back the previous few transactions and restore the system to the state it was in, say, two hours ago. However, if you want to be selective, “upgrading” to an older version is actually more convenient than it would be to try to select a rollback transaction that contains the change you have in mind.