Wallaby stores versioned configurations in a database. Wallaby API clients can access older versions of a node’s configuration by supplying the version option to the Node#getConfig method. Sometimes, though, we’d like to inspect individual configurations in greater detail than the API currently allows.

The Wallaby git repository now contains a command to export versioned configurations from a database to flat text files. Clone the repository or just download the file and then place cmd_versioned_config_export.rb somewhere in your WALLABY_SHELL_COMMAND_PATH, and you’ll be able to invoke it like this:

Usage:  wallaby vc-export DBFILE
exports versioned configurations from DBFILE to plain text files
    -h, --help                       displays this message
        --verbose                    outputs information about command progress
    -o, --output-dir DIR             set output directory to DIR
        --earliest NUM               output only the earliest NUM configurations
        --latest NUM                 output only the most recent NUM configurations
        --since DATE                 output only configurations since the given date

It will create a directory (called snapshots by default) with subdirectories for each versioned configuration; each of these will be timestamped with the time of the configuration. Within each version directory, there will be directories for node configurations and stored group configurations. (If you’re using an older version of Wallaby, the only stored group configuration will be for the default group. Versioned configurations generated with a fairly recent version of Wallaby, on the other hand, will have stored configurations for more groups and should also have useful information about node memberships in the configuration file.)

(cross-posted from getwallaby.com)

Wallaby paper at SC11

| No Comments

I'm pleased to announce that our paper "Wallaby: A Scalable Semantic Configuration Service for Grids and Clouds" will be presented at SC11 in the "State of the Practice" track on Clouds and Grids. If you're going to be at SC, I hope we'll be able to chat about Wallaby!

Ruby constants are a nice place to put application configuration information, but they can be inflexible if you want to defer initialization until later — for example, if you want a constant to have a given value at application startup only if a certain environment variable or command–line parameter is set. I like the idea of the single–assignment variables that you get in many functional and logic languages, and I also like the idea that constants only really need to be constant after they've been read once. (See this paper for some interesting implications of that idea in Java programs.)

The quiescent gem is a little hack I put together to make it easy to have write-once, defaultable constants in your Ruby programs. A quiescing constant, or quiescent, has an optional default value (either an explicit value or a block to calculate that value), and can be assigned to at most once in a program execution. Once the quiescent is written to, its value quiesces and it becomes a normal constant. If it is read without being explicitly quiesced, it assumes the default value (or the result of executing the default-value block) and becomes a normal constant. Here's an example to make things clearer:

Get quiescent from GitHub or from RubyGems.

Earlier today, I released version 0.2.0 of Gliss, a lightweight tool for inspecting and processing tagged annotations in git repositories. Since I last wrote about gliss, there have been a few improvements worth mentioning; I’ll briefly go over them here:

  • Support for malformed glosses. The gliss conventions specify that a gloss tag must start at the beginning of a new line. If you have some commits that don’t follow the conventions, though, you might miss their glosses. By using the --permissive option, you can find gloss candidates that are indented or that start in the middle of the line.
  • Support for multiple tag filters. In previous releases of gliss, you could use the --filter option to specify a regular expression that all printed tags must match. Now, supplying multiple --filter options will return all glosses that match any of the provided expressions.
  • Support for printing the whole commit in which matching glosses appear. The --whole-commit option (`-w for short) will cause gliss to print every commit containing a matching gloss. You may need this option if you’re compiling release notes and the gloss text for a given gloss doesn’t contain enough context.
  • Support for Markdown-formatted glosses. If you have the Maruku gem installed, gliss can treat glosses with certain tags as containing Markdown-formatted text and render them in well-formed HTML or LaTeX. Use the --format-glosses-matching option to specify regular expressions for gloss tags whose texts should be rendered via Markdown and --markdown-output to specify LaTeX or HTML output. This is especially useful for streamlining automatic documentation generation from gloss texts.

Install the latest version of gliss with gem install gliss (and, optionally, gem install maruku).

One aspect of gliss that some users who don’t have much exposure to git find confusing is how it selects a range of commits to process. Ordinarily, you’ll invoke gliss with two arguments: FROM and TO, both of which can be SHAs, branch names, or tag names. The gliss on-line help tells you that this will process all of the commits reachable from TO but not from FROM. If this doesn’t make a lot of sense, or if you aren’t sure what it means to be “reachable” from a branch or tag, read on.

Example git repository

The image above shows a simple git repository. In this image, commits have a single-letter identifier and only contain a pointer to their immediate ancestor. By transitively following the ancestry relationship, we can get from any commit to the root commit of the repository. Note that git commits under the ancestry relation form a graph; more specifically, it is a directed graph (because ancestry is not a symmetric relation) and an acyclic graph (because a node may not transitively have itself as an ancestor).

In this diagram, we have two branches: master and topic, and two tags: 1.1 and 1.0. Tags are essentially immutable pointers to particular commits; branches are pointers that can track a stream of development. A is the root commit, B is the commit pointed to by 1.0, E is the commit pointed to by 1.1, and so on. Let’s look at some example gliss use cases to see how we can use this information.

Say we wanted to see what had changed between tags 1.0 and 1.1. We’d give 1.0 as the FROM argument and 1.1 as the TO argument. This would examine all of the commits reachable from 1.1, but not from 1.0. The commits reachable from 1.1 are A, B, C, D, and E (E is a merge commit, which is why it has multiple parents); of these, B and A are also reachable from 1.0. Therefore, in this case, gliss would only search for glosses in C, D, and E.

Alternatively, we might want to see which commits were reachable from topic but not from master. In this case, gliss would process only F. Note, however, that the TO and FROM arguments are not interchangeable: using gliss on the commits reachable from master but not from topic would give us the glosses C, E, and G.

Please contact me or file a issue report if you have problems with or suggestions for gliss. I hope you find it useful!

Earlier today, I released version 0.4.0 of Rhubarb, a little object-graph persistence library for Ruby built on top of SQLite. Rhubarb 0.4.0 adds no new features, unless you consider “working with recent releases of the sqlite3 gem” to be a feature. The case of Rhubarb 0.4.0 is interesting, though, because it exposes a fundamental incompatibility between the package-dependency model of RubyGems and that of operating-system distributions.

The sqlite3 gem (formerly known as ruby-sqlite3) introduced some nontrivial semantic and interface incompatibilities between versions 1.2 and 1.3; as far as I can tell, 1.3 is a near-total rewrite. The main problems I ran into with Rhubarb were related to prepared statements and to the (now deprecated and subtly broken) “type translation” functionality that is meant to take untyped SQLite values and convert them to Ruby objects of an appropriate type.1

In some circles, backwards incompatibility is anathema, especially if (as in this case) it entails deprecating useful functionality and providing no replacements or workarounds. However, in the Ruby world, it essentially doesn’t matter: the RubyGems package manager is designed to let you install multiple versions of a gem package simultaneously; gems can specify that they will only work with a particular minor-version series of a dependency; and there are a whole host of external tools to make it easier to install an application in a private, application-specific local environment with the correct versions of its dependencies.2

Since production Ruby applications can run indefinitely against an older version of a dependency without affecting the rest of the system, there is really little disincentive to breaking backwards compatibility. Unless, that is, someone wants to package production Ruby applications as part of Fedora or another Linux distribution. In that case, the packager is likely stuck with just the most recent stable version of the dependency — and this version must work for every application packaged with the distribution. Since it is typically not an option for downstream packagers to install “local” versions of packages, this means that they are faced with an unappealing choice: break (and thus don’t ship) some software or package unnecessarily conservative versions of dependencies.

This problem is especially interesting since, due to the impedance mismatch between these packaging cultures, there is no obviously correct solution.



1 I observed several related problems that led to a lot of test suite and application failures:

  1. In the 1.2 series, automatic type translation caused field values to be converted based on the given type of the column; in newer versions (in which automatic type translation is officially deprecated), conversion proceeds based on properties of the string representation of the value. So, for example, a varchar field containing the value “fooblitz” would become a Ruby String, but a varchar field containing the value “1234” would become a Ruby Fixnum.
  2. Enabling (deprecated) automated type-translation support in the version of sqlite3-ruby in Fedora 15 breaks the results_as_hash facility that allows addressing rows by column name and not merely column index. (I understand this is fixed in a more recent release.)
  3. Attempting to install explicit type translations results in a deprecation warning, leaving no reasonable way to work around the deprecation of automatic type translation.
  4. Foreign-key triggers are apparently now incompatible with prepared statements, resulting in confusing failures upon database close if both are enabled.

2 While the local-environment idea is well-known, notably in the Java world and anywhere that people ship software that uses Boost, some environments take the basic philosophy further still. Consider the build process for applications using the webmachine REST framework, which clones and checks out particular versions of webmachine’s dependencies!

In an earlier post, I presented a technique for adding node tagging to Wallaby without adding explicit tagging support to the Wallaby API. Node tags are useful for a variety of reasons: they can correspond to informal user-supplied classifications of nodes or machine-generated system attributes (e.g. "64-bit", "high-memory", "infiniband"). Since we implemented tags as special Wallaby groups, they may contain configuration information (although they don't need to) and will be advertised as one of the machine's Condor attributes.

The Wallaby source repository now includes a small Python module that patches the Wallaby python client library with idiomatic tagging support: namely, you can ask the store for the name of the group that represents the partition between tags and groups, and you can call getTags() and modifyTags() methods on Node objects, as well as inspect the tags property of a Node. This will appear in Wallaby packages in the future, but you don't have to be that adventurous to try it out now! To use it, clone the repository and put extensions/tagging.py and schema/wallaby.py somewhere in your PYTHONPATH. Then follow along with this transcript:

I appreciate any feedback or comments on this library.

In this post, I’ll sketch a half-baked plan for making an idiomatic RESTful service that handles versioned data in a sensible way. I’m not claiming that the pattern I’m proposing is novel or non-obvious (it is, in fact, rather obviously inspired by git and functional dictionaries); but I haven’t seen it elsewhere, and I believe it might be a good pattern for other folks with similar applications to mine. I’ll briefly introduce my problem domain (specifically, a web-service interface for Wallaby) before detailing my solution. (Feel free to skip the first section if you aren’t interested in the Wallaby application, and skip the second if you have a pretty good idea of how git works.)

Background

Wallaby’s current API maps quite naturally to remote invocations over XML-RPC or SOAP, but a REST interface is more palatable to many client programmers. (And, perhaps more importantly, to this service developer: as a recovering functional programmer, I really appreciate the explicit state transfers inherent in RESTful interaction.) A more dramatic interface change affords the opportunity to think about improving some limitations of the old interface, particularly in terms of the versioning model.

Wallaby’s current versioning support is designed to support two major kinds of operations: saving and reloading the state of the entire system, and comparing the (activated) configuration of an individual node to any previously-activated configuration. These two classes of operations support a lot of interesting use cases really well, including most of the ones Wallaby users care about: providing named snapshots of system state, providing “undo” functionality, and differencing configurations to identify which Condor subsystems need to be restarted.

However, this model has some shortcomings: it treats service state as essentially linear (not as a tree or DAG), it is thus unable to support branches (or to safely handle concurrent modification), and it is unable to optimize cases involving identical snapshots or partial snapshots with distinct identities. In practice, these are not a major obstacle to using Wallaby, but it would be nice to eliminate these problems and, more generally, to support branching and tree-structured state histories.

A quick overview of the git model

The model of versioned data that git presents seems like a pretty sensible starting point: put simply, this model is a content-addressable memory, where objects are stored by hashes of their contents. Objects can be untyped data blobs (think of the contents of files); trees, which contain mappings from names to trees or blobs; or commits, which bundle metadata including a timestamp, an author, and the identity of the parent commit with a tree. The ancestor-of relation implies a DAG of commits. (git supports more features, and its data model is more complex than the brief explanation above, but this non-exhaustive summary should suffice for the purposes of this post.) Objects are write-once and can thus be shared: files and subtrees among multiple trees, commits as parents of multiple commits, etc.

git also supports naming particular commits: tags are names that are fixed to particular commits, and branches are names that can be updated to refer to a commit transitively descended from the commit that they currently point to. (Of course, it is possible in practice to “update” a branch to an unrelated commit, or to “update” a tag, by deleting and recreating it, but these operations are not directly provided by the interface.) All branches and tags, taken collectively, are known as the heads of a repository; typically (but not always), you can follow the ancestry relation back to a single primeval commit from any head.

One common pattern for multiple users collaboratively editing a git repository involves creating clones of that repository, one for each user. Each user then works in his or her clone repository, editing and making commits. When the time comes to share these changes, the user can then push changed branches and new tags from the clone to the original repository. Other users can then pull these changes from the original repository and merge these in to their private clones.

Clearly, the git model has some similarities to REST: client expectations and full object state are transferred explicitly between repositories (so that, e.g., you can’t push an updated branch that isn’t an ancestor of the remote branch you’re pushing to). However, the assumption that each client can make changes to a private, persistent working area and transmit them en masse does not generally hold in the web services domain, especially if we want clients to be essentially stateless.

RESTful versioned data

The basic idea behind this proposal is that “changing” an object simply creates a new object, as with git and functional data structures. The identities of data objects are taken from their content; tree objects contain mappings from names to particular tree or data objects. (These names provide ways to model change, since object identities are dependent on object contents.) Clients who access the state of the system are really accessing a particular named state — that is, the state of a tree with a given (mutable) tag. Clients can construct substantial changes to the state of the system in parallel to the version that other clients are accessing, and then change the whole “state of the system” atomically by changing the identity of the current-state tag. We’ll now spend some time unpacking this fairly dense overview.

The figure below presents an example git-like representation of an employee database; it will be our running example. Note that, as with a git repository, there is a commit object pointing to a tree object; trees contain references either to other tree objects or to data objects. (In the figure, commit objects are orange, tree objects are blue, and data objects are red.)

Employees

For clarity, this figure omits some data objects and does not provide explicit IDs for trees. Note that data objects are represented as strings containing structured data. (It would also be possible to represent data as trees mapping from attribute names to structured-data representations of values.) Note also that inter-object references, as in the object representing the Engineering department, are handled by name (i.e. the path through the tree to the referenced object) and not by hashes or other unique identifiers; this makes it possible to change a single object without affecting other objects.

We’ll now cover what happens when we access and modify data. We’ll present one way to access data and two ways to update it, using two different API approaches: a high-level approach and a low-level approach.

Accessing data

We will use HTTP GET requests to access objects or collections of objects. Consider the following examples:

GET /tag/master/employees/wilma
Redirects to a URL explicitly identifying the current state of the system on tag master, something like /commits/89d71040/employees/wilma. This URL points to a representation of the state of /employees/wilma as of commit 89d71040; that is, the object with identity 2c8ec116.
GET /objects/2c8ec116
Returns the representation of the object with identity 2c8ec116.
GET /commits/89d71040/employees/
Returns the name and object ID of every employee as of commit 89d71040, like {“wilma”:2c8ec116, “betty”:3fd513f6, …}
GET /commits/89d71040/employees/?displayname=Flintstone
Returns the name and object ID of every employee as of commit 89d71040 whose displayname attribute contains the string “Flintstone.”

Updating data: the high-level approach

The high-level approach corresponds fairly closely to the managed-object model: one object can be changed at a time, and the changes to that object are immediately committed to service state. However, because we don’t want to force users to update the externally-visible service state (that is, the state of the master tag) with each object update, we will not change the referent of the master tag. Instead, updating an object will create a “detached commit,” as in the following example, in which we want to move Wilma to the corner office next door:

Updated wilma

Here’s how we’d update the system to reflect this new state for the wilma object:

  1. A client issues a HTTP PUT request to /tag/master/employees/wilma; at the point of the request, the master tag points to commit 89d71040 and /employees/wilma points to object 2c8ec116. Included in the PUT request body is a representation of the updated state for wilma.
  2. The service creates a new object with the supplied updated state (a30db7ac) for wilma.
  3. The service then creates a new tree object for /employees with the wilma reference updated to point to a30db7ac.
  4. The service then iteratively updates every tree containing an updated tree until it gets to the root.
  5. The service creates a new commit object referencing the newly-created root tree with 89d71040 as a parent commit; say that this new commit has the ID 597028a0.
  6. The PUT request returns, redirecting the client to /commits/597028a0/employees/wilma. Until this commit object becomes transitively reachable from a named tag, it will have a Content-Expires header indicating a point after which it may be garbage collected. The client may inspect the commit object or use it as a point of reference for further updates.

After making several changes, the client may wish to update the master tag, thus affecting the default externally-visible state of the system. In this approach, as in the low-level approach below, updating tags is done with a PUT request. To eliminate the possibility of inconsistent updates across clients, the PUT request body should include both the expected old hash of the tag and the new tag hash; this allows rejecting updates that would shadow other updates. Once a commit is tagged, all of its ancestors are marked as reachable so that they will not expire.

Updating data: the low-level approach

This model makes explicit what happens when changing the state of the system. Say we want to move Wilma to the corner office next door, as above. First, we’ll create a new object corresponding to Wilma’s new state, using HTTP POST to store the new payload, with a request like POST /objects and a payload of the representation of the new object. After this request succeeds, we have the following state:

Updated object

Note that the /employees tree still points to the old wilma object. The client is responsible for creating a new tree object that points to the new wilma object, which it does by HTTP POST request to the /trees resource. The client must then create a new tree object for the tree containing the modified employees tree; this is the root tree in this case, but in general the process recursively creates parent trees until it creates a new root. Given the identity of the new root tree object, the client can then create a new (detached) commit object, proceeding as in the high-level model.

This low-level approach trades flexibility (specifically, the ability to make commits with multiple changes) for complexity, either in the client or (more likely) in a client library. For applications that must support atomic operations on multiple objects, it might be more suitable than the high-level model; as stated above, though, the high-level model is well-suited for migrating applications from a managed-objects model.

Improvements

There are a lot of features that git has that we’ve omitted or hinted at in the above discussion. Here, we’ll briefly examine a few that it makes sense to support:

  1. Differencing and patching. It should be possible to construct the difference of two trees. (This may be impractical in general, but it should be sensible for trees of data objects that can be distinguished and differenced.) Furthermore, it should be possible to construct a patch object that encapsulates the difference between two trees (i.e. how to transform tree A into tree B) and can be applied to other trees to incorporate these changes.
  2. Merging. Given differencing and patching, it makes sense to have first class support for constructing new commits that are the merge of two or more other commits.
  3. Hooks. It should be possible to specify tests which must succeed before, e.g., a commit succeeds, or before a commit may receive a particular tag. This allows the system to enforce validity properties.
  4. Garbage collection. The system should be space-efficient, automatically eliminating unreachable commits that have expired.

Going forward from here

It seems like this pattern makes sense for my application, but it also seems sufficiently general to be more broadly useful. I’d be inclined to implement this pattern as a framework that handles most of the “git plumbing”-type operations for developer-provided model or resource objects or structures. In fact, it might also make sense to support automatic generation of client libraries for certain classes of application! Dealing with versioned data is a tedious hassle. Anything we can do to make it more straightforward is a huge win.

I welcome constructive feedback on this proposal.

UPDATE: clarified a couple of confusing sentences; thanks to Erik Erlandson for bringing them to my attention.

One of the great things about Wallaby is that it's a platform, not merely a tool. Put another way, if it doesn't do exactly what you want, you can use its API to build tools that benefit from configuration validation and deployment. We've talked in the past about a number of useful tools built on the Wallaby API. (Another cool Wallaby API client that I hope to talk about more in the future is Erik Erlandson's Albatross project, which programmatically generates and changes pool configurations in order to test Condor scale and functionality.)

The Wallaby API is designed to be sufficiently general to allow developers to do just about anything with configuration data, not to unnecessarily restrict users to a few use cases that we thought of. Because of this generality, some tasks might require adopting application-level conventions. In this article, we'll cover one such convention and see how the Wallaby API is flexible enough to handle an interesting use case — namely, tagging nodes with various keywords, perhaps as supplied by a user or generated by an agent like sesame or matahari.

First, though, we'll review how configurations are generated and applied to nodes. Recall that a node is a member of several groups. These memberships are ordered: a node's lowest-priority membership is always in the special default group (which includes every node), and its highest-priority membership is always in a special, node-specific identity group (which only includes a single node). Zero or more memberships in explicit groups may occupy the priority space between the default group and a node's identity group. In the illustration below, node.local is a member of two explicit groups, which have blue backgrounds: "EC2 submit nodes," and "Execute nodes."

Wallaby groups simple

When Wallaby calculates the configuration for node.local., it will begin with a copy of the default group's configuration. It will then repeatedly apply the configurations for the explicit groups and identity group in order of increasing priority, so that parameter-value mappings from higher-priority groups take precedence over lower-priority ones (and thus either replace these or are appended to them, depending on the mapping kind). The condor_configd, which takes a node's configuration from Wallaby and installs it on a node, will also cause the Wallaby groups a node is a member of (as well as the Wallaby features it has installed) to be advertised for use in Condor matchmaking. So a Condor job could specify that it wanted to match against a node that was a member of the "EC2 submit nodes" group; this would translate into a preference for node.local.

Because group names appear in machine ClassAd attributes, the list of explicit node memberships is a natural place to put tag information. Nodes would simply be members of "dummy" groups like "Desktop workstations" or "Machines with more than 8GB RAM," and these keywords could be used in matchmaking or in searching for particular nodes. However, the list of explicit groups is not necessarily suitable for automatic manipulation: users will not necessarily expect their changes to be overridden, other API clients won't necessarily expect users to rearrange or remove groups corresponding to tags, and in general it is impossible to determine whether a group membership should be interpreted as a tag or as an explicit membership.

We can adopt a convention to partition the space of group memberships. Say we create a special sentinel group to partition the membership space: every node will be a member of the sentinel group. All memberships that are of a lower priority than the sentinel group will be managed by tagging agents, and all memberships that are of a higher priority will be explicitly managed by the user. In the example below, "—EXPLICIT GROUPS" is the sentinel group, and groups in yellow correspond to tags.

Wallaby memberships, with a sentinel group and tag groups

This approach demonstrates the generality of the Wallaby API, and allows users to supply tag-specific configurations by installing parameters or features on tag groups, for example, ensuring that every desktop workstation has a policy that favors its owner, or overprovisioning high-memory nodes. However, it is not free of shortcomings. Firstly, Wallaby will present all of these groups — sentinel, tag, and explicit groups — so a user-facing tool that puts a friendly interface on node memberships and tagging will need to use the sentinel group to partition which groups it displays to the user. Secondly, the sentinel groups must be added to all nodes (in practice, we can assume that the absence of a sentinel group implies the absence of tags); in general, we are relying on clients to enforce invariants in this case. Finally, altering a node's membership list to insert a tag will cause that node's configuration to be validated at the next activation. If the node has a complex configuration, this could be expensive.

Rob Rati and I presented a Wallaby user tutorial at Condor Week yesterday. Today, we have a tutorial you can follow along with at home, including a link to an EC2 AMI that boots up with Wallaby and Condor installed and running so you can experiment without installing anything. Enjoy!

Grepping for git glosses with gliss

| No Comments

I'm pleased to announce the first release of gliss, a tool to make it easier to track lightweight inline annotations in your git repositories. gliss is available as source code or as a Ruby gem.

Consider the following things you might want to do if you could put metadata in your commit messages:

  • You'd like to link commits to the bug-tracking tickets that they addressed.
  • You'd like to identify commits that other developers have signaled might require a release note (or, worse yet, introduce a backwards-compatibility problem).
  • You're working on a private branch and you'd like to mark some commits for later redaction or revision.

If you're willing to adopt a simple, flexible microformat for metadata annotations in commit messages, you can write annotations expressing all of these and more, and then use the gliss tool to identify all of the annotations (optionally of a certain kind) in a range of commits (or in the difference between two trees). We'll begin by introducing the format for these annotations.

A gliss annotation is called a gloss and has two parts: a tag, and (optionally) some text. To put a gloss in your commit message, simply begin a line with three identical characters, which we'll call the tag delimiter, followed by a tag name, which can be any string of characters that doesn't start with a dash or contain the tag delimiter, and then the tag delimiter again. Examples include ---FRED---, ===BARNEY===, and ***WILMA***. gliss will recognize lines that contain gloss tags and process them. Any additional text on the same line as the gloss tag will be treated as the beginning of the gloss text.

A gloss can contain more than one line; simply indent the first line after the line containing the gloss tag, and this line becomes a continuation line: any text after the indent will be appended to the gloss text. Similarly, any line after a continuation line will be a continuation line if it is at least as indented as the first continuation line of its gloss. (Currently, the gliss tool strips extra whitespace from continuation lines before outputting them; in a future release of gliss, glosses whose tag begins with a dash will be outputted with whitespace and formatting preserved.)

As an example, consider this commit message from the gliss repository, which contains two glosses, both tagged @EXAMPLE. The first is contained on a single line. The second contains multiple continuation lines; its ends with the first non-indented line.

To inspect these messages with the gliss tool, make sure you have gliss installed and the gliss repository checked out locally. You can then run gliss 2ce2b3c2 master from within the gliss repository directory, and it will show you all of the glosses in commits that are ancestors of master but are not ancestors of 2ce2b3c2. (You can omit master from the command-line; it's the default.) The output will look something like this:

172863f7 (@EXAMPLE)

    This is an example gliss message, tagged @EXAMPLE.

172863f7 (@EXAMPLE)

    This is another example gliss message, that spans multiple lines. 
    As long as each line has at least the same amount of indentation 
    as the first line after the tag, messages can span one or many lines.

That's it.

The gliss tool also supports some options, notably to display only glosses with certain tags. Try gliss -h for some brief documentation overview.

I wrote gliss because I intend to use it, but I hope you find it useful, too. Please don't hesitate to send in feedback or bug reports.

Find recent content on the main index or look in the archives to find all content.

About Chapeau

  • I work for Red Hat on the MRG project. I hold a PhD in computer sciences from the University of Wisconsin, where I mainly worked on program analysis and concurrency.
  • On this site, I write about topics related to things I'm working on now and things I've worked on in the past: distributed computing and programming languages. I don't speak for my employer, and any opinions on this site are mine alone.

Recent Comments

  • Will Benton: Erik, I absolutely agree; this should be considered early-access stuff. read more
  • Erik Erlandson: It might be better to organize as: import wallaby.tagging read more
  • ferkeltongs: Hi Will, I came across your post while looking for read more

Recent Assets

Categories

Pages

Powered by Movable Type 4.25