aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit/META-INF/MANIFEST.MF
Commit message (Collapse)AuthorAgeFilesLines
...
* Prepare post 2.2.0.201212191850-r buildsstable-2.2Matthias Sohn2012-12-211-1/+1
| | | | Change-Id: I1a0fe51c71551fcfc98f5dd435eb283fd661b77a Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* JGit v2.2.0.201212191850-rv2.2.0.201212191850-rMatthias Sohn2012-12-201-1/+1
| | | | | Change-Id: Idc49f17d03886b6a1e61a94ff81e32625c8675d9 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* Import non-java.* JRE packagesMatthias Sohn2012-11-161-0/+4
| | | | | | | | | | | Otherwise loading javax.net.ssl.TrustManager fails if osgi.compatibility.bootdelegation=false which became the Equinox default since bug 344850 was fixed. Bug: 392056 Change-Id: I464871723649095942dbf77da93890ac8ec39075 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com> Signed-off-by: Chris Aniszczyk <zx@twitter.com>
* Use x-friends instead of x-internal to expose jgit for internal useRobin Rosenberg2012-09-191-1/+1
| | | | | | | This prevents a lot of unnecessary warnings about disouraged usage of the org.eclipse.jgit.internal package within JGit itself. Change-Id: Ia6683902809425fd7245e7d5d344c2ff8f317ebb
* Prepare 2.2.0 buildsMatthias Sohn2012-09-191-29/+29
| | | | | Change-Id: I386ba70541d644e58661d26713b309371e0f9257 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* Prepare 2.1.0 buildsMatthias Sohn2012-06-141-29/+29
| | | | Change-Id: I4aad3efdd435d8d5eb53c84a8d38132acce97c25 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* Do not import/export empty org.eclipse.jgit packageTomasz Zarna2012-03-131-2/+1
| | | | | | The package was removed in I763590a45d75f00a09097ab6f89581a3bbd3c797 Change-Id: Ifa9e75714f85d17609f9bf61581aaed0631a6fa7 Signed-off-by: Kevin Sawicki <kevin@github.com>
* Move JGitText to an internal packageRobin Rosenberg2012-03-121-0/+1
| | | | Change-Id: I763590a45d75f00a09097ab6f89581a3bbd3c797
* Prepare 2.0.0-SNAPSHOT buildsMatthias Sohn2012-02-161-29/+29
| | | | | Change-Id: I946e315af04227727ac937ebe9d70ae1ea4e8936 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* Add comand support for git-submoduleKevin Sawicki2011-12-281-0/+1
| | | | | | | | | | | | | | | | Adds the following commands: - Add - Init - Status - Sync - Update This also updates AddCommand so that file patterns added that are submodules can be staged in the index. Change-Id: Ie5112aa26430e5a2a3acd65a7b0e1d76067dc545 Signed-off-by: Kevin Sawicki <kevin@github.com> Signed-off-by: Chris Aniszczyk <zx@twitter.com>
* Prepare 1.3.0 buildsMatthias Sohn2011-12-101-28/+28
| | | | | Change-Id: I7a1ae73783c95041b59f047a7330e62e7f642149 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* DFS: A storage layer for JGitShawn O. Pearce2011-11-041-0/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | In practice the DHT storage layer has not been performing as well as large scale server environments want to see from a Git server. The performance of the DHT schema degrades rapidly as small changes are pushed into the repository due to the chunk size being less than 1/3 of the pushed pack size. Small chunks cause poor prefetch performance during reading, and require significantly longer prefetch lists inside of the chunk meta field to work around the small size. The DHT code is very complex (>17,000 lines of code) and is very sensitive to the underlying database round-trip time, as well as the way objects were written into the pack stream that was chunked and stored on the database. A poor pack layout (from any version of C Git prior to Junio reworking it) can cause the DHT code to be unable to enumerate the objects of the linux-2.6 repository in a completable time scale. Performing a clone from a DHT stored repository of 2 million objects takes 2 million row lookups in the DHT to locate the OBJECT_INDEX row for each object being cloned. This is very difficult for some DHTs to scale, even at 5000 rows/second the lookup stage alone takes 6 minutes (on local filesystem, this is almost too fast to bother measuring). Some servers like Apache Cassandra just fall over and cannot complete the 2 million lookups in rapid fire. On a ~400 MiB repository, the DHT schema has an extra 25 MiB of redundant data that gets downloaded to the JGit process, and that is before you consider the cost of the OBJECT_INDEX table also being fully loaded, which is at least 223 MiB of data for the linux kernel repository. In the DHT schema answering a `git clone` of the ~400 MiB linux kernel needs to load 248 MiB of "index" data from the DHT, in addition to the ~400 MiB of pack data that gets sent to the client. This is 193 MiB more data to be accessed than the native filesystem format, but it needs to come over a much smaller pipe (local Ethernet typically) than the local SATA disk drive. I also never got around to writing the "repack" support for the DHT schema, as it turns out to be fairly complex to safely repack data in the repository while also trying to minimize the amount of changes made to the database, due to very common limitations on database mutation rates.. This new DFS storage layer fixes a lot of those issues by taking the simple approach for storing relatively standard Git pack and index files on an abstract filesystem. Packs are accessed by an in-process buffer cache, similar to the WindowCache used by the local filesystem storage layer. Unlike the local file IO, there are some assumptions that the storage system has relatively high latency and no concept of "file handles". Instead it looks at the file more like HTTP byte range requests, where a read channel is a simply a thunk to trigger a read request over the network. The DFS code in this change is still abstract, it does not store on any particular filesystem, but is fairly well suited to the Amazon S3 or Apache Hadoop HDFS. Storing packs directly on HDFS rather than HBase removes a layer of abstraction, as most HBase row reads turn into an HDFS read. Most of the DFS code in this change was blatently copied from the local filesystem code. Most parts should be refactored to be shared between the two storage systems, but right now I am hesistent to do this due to how well tuned the local filesystem code currently is. Change-Id: Iec524abdf172e9ec5485d6c88ca6512cd8a6eafb
* Prepare 1.2.0 buildsMatthias Sohn2011-09-151-27/+27
| | | | | Change-Id: I9ec247135d93ef28d732e94f18d0ec1d0e2e6d44 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* Prepare post v1.1.0.201109151100-r buildstable-1.1Matthias Sohn2011-09-151-1/+1
| | | | | Change-Id: Ib099ec93d8243b238641d79328216874532ab5eb Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* JGit v1.1.0.201109151100-rv1.1.0.201109151100-rMatthias Sohn2011-09-151-1/+1
| | | | | Change-Id: Iadcec7e5973600e005cbdeb837fa197d3ae2ea86 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* Prepare post v1.1.0.201109071825-rc3 buildsMatthias Sohn2011-09-081-1/+1
| | | | | Change-Id: I1244f6639263d156a6f9e4530167e5eb1826a535 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* JGit v1.1.0.201109071825-rc3v1.1.0.201109071825-rc3Matthias Sohn2011-09-081-1/+1
| | | | | Change-Id: I1b989d3101272632eacabe25a0b111ad0ff5bb3b Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* Prepare post-v1.1.0.201109011030-rc2 buildsMatthias Sohn2011-09-011-1/+1
| | | | Change-Id: I8dda83cdbe88beba4a480df9846848bf3aceb9e2 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* JGit v1.1.0.201109011030-rc2v1.1.0.201109011030-rc2Matthias Sohn2011-09-011-1/+1
| | | | | Change-Id: Ie6d65fe45ad92c813ce3a227729aa43681922249 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* Prepare 1.1.0 buildsMatthias Sohn2011-06-061-27/+27
| | | | | Change-Id: I4cf017cd567543846839612ab3ace6d26233e01d Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* Prepare post v1.0.0.201106011211-rc3 buildsMatthias Sohn2011-06-011-1/+1
| | | | | Change-Id: I4dec8eba7e35858aef65fcc10f91fad3fe5b52b9 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* JGit v1.0.0.201106011211-rc3v1.0.0.201106011211-rc3Matthias Sohn2011-06-011-1/+1
| | | | | Change-Id: I574a05200471c431b3a02ac6ff208dc6aa90f539 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* blame: Compute the origin of lines in a result fileShawn O. Pearce2011-05-311-0/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | BlameGenerator digs through history and discovers the origin of each line of some result file. BlameResult consumes the stream of regions created by the generator and lays them out in a table for applications to display alongside of source lines. Applications may optionally push in the working tree copy of a file using the push(String, byte[]) method, allowing the application to receive accurate line annotations for the working tree version. Lines that are uncommitted (difference between HEAD and working tree) will show up with the description given by the application as the author, or "Not Committed Yet" as a default string. Applications may also run the BlameGenerator in reverse mode using the reverse(AnyObjectId, AnyObjectId) method instead of push(). When running in the reverse mode the generator annotates lines by the commit they are removed in, rather than the commit they were added in. This allows a user to discover where a line disappeared from when they are looking at an older revision in the repository. For example: blame --reverse 16e810b2..master -L 1080, org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefDirectoryTest.java ( 1080) } 2302a6d3 (Christian Halstrick 2011-05-20 11:18:20 +0200 1081) 2302a6d3 (Christian Halstrick 2011-05-20 11:18:20 +0200 1082) /** 2302a6d3 (Christian Halstrick 2011-05-20 11:18:20 +0200 1083) * Kick the timestamp of a local file. Above we learn that line 1080 (a closing curly brace of the prior method) still exists in branch master, but the Javadoc comment below it has been removed by Christian Halstrick on May 20th as part of commit 2302a6d3. This result differs considerably from that of C Git's blame --reverse feature. JGit tells the reader which commit performed the delete, while C Git tells the reader the last commit that still contained the line, leaving it an exercise to the reader to discover the descendant that performed the removal. This is still only a basic implementation. Quite notably it is missing support for the smart block copy/move detection that the C implementation of `git blame` is well known for. Despite being incremental, the BlameGenerator can only be run once. After the generator runs it cannot be reused. A better implementation would support applications browsing through history efficiently. In regards to CQ 5110, only a little of the original code survives. CQ: 5110 Bug: 306161 Change-Id: I84b8ea4838bb7d25f4fcdd540547884704661b8f Signed-off-by: Kevin Sawicki <kevin@github.com> Signed-off-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com>
* Qualify post-0.12 buildsMatthias Sohn2011-05-031-26/+26
| | | | | Change-Id: I70fe2671321efb5c3d271121ce00299533d1b388 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* JGit 0.12.1v0.12.1stable-0.12Matthias Sohn2011-05-021-26/+26
| | | | | Change-Id: Ia6e58b466fa3ef7ddd61b40f2ad44141fe8786c4 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* daemon: Use HTTP's resolver and factory patternShawn O. Pearce2011-02-141-0/+1
| | | | | | | | | | | | | | | | | | | Using a resolver and factory pattern for the anonymous git:// Daemon class makes transport.Daemon more useful on non-file storage systems, or in embedded applications where the caller wants more precise control over the work tasks constructed within the daemon. Rather than defining new interfaces, move the existing HTTP ones into transport.resolver and make them generic on the connection handle type. For HTTP, continue to use HttpServletRequest, and for transport.Daemon use DaemonClient. To remain compatible with transport.Daemon, FileResolver needs to learn how to use multiple base directories, and how to export any Repository instance at a fixed name. Change-Id: I1efa6b2bd7c6567e983fbbf346947238ea2e847e Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
* Qualify post 0.11 buildsMatthias Sohn2011-02-121-25/+25
| | | | | Change-Id: Ibcef4fc4c986c2cda01e943d16aa1c53eff99f25 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* JGit 0.11.1v0.11.1Matthias Sohn2011-02-111-25/+25
| | | | | Change-Id: I9ac2fdfb4326536502964ba614d37d0bd103f524 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* Qualify post 0.10 buildsMatthias Sohn2010-12-171-25/+25
| | | | | Change-Id: Ifcb8fdea95286779c8aea6bf4d7647e8c1c98d63 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* Qualify post 0.10.1 buildsstable-0.10Matthias Sohn2010-12-171-25/+25
| | | | | Change-Id: I320f1f739f3689daf11d532a55ae1133785aec8e Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* JGit 0.10.1v0.10.1Matthias Sohn2010-12-171-25/+25
| | | | | Change-Id: I4a46d35d354193e5d4f28ef7dfae75944be8ffcf Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* Define NoteMap, a simple note tree readerShawn O. Pearce2010-11-111-0/+1
| | | | | | | | | | | | | | | | | | | | | | | | | The NoteMap makes it easy to read a small notes tree as created by the `git notes` command in C Git. To make the initial implementation simple a notes tree is read recursively into a map in memory. This is reasonable if the application will need to access all notes, or if there are less than 256 notes in the tree, but doesn't behave well when the number of notes exceeds 256 and the application doesn't need to access all of them. We can later add support for lazily loading different subpaths, thus fixing the large note tree problem described above. Currently the implementation only supports reading. Writing notes is more complex because trees need to be expanded or collapsed at the exact 256 entry cut-off in order to retain the same tree SHA-1 that C Git would use for the same content. It also needs to retain non-note tree entries such as ".gitignore" or ".gitattribute" files that might randomly appear within a notes tree. We can also add writing support later. Change-Id: I93704bd84ebf650d51de34da3f1577ef0f7a9144 Signed-off-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com>
* Qualify builds as 0.10.0Shawn O. Pearce2010-09-161-24/+24
| | | | | Change-Id: I54815c85b32b9492c059064b39f48677e68c5e90 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
* Qualify post-0.9.3 buildsstable-0.9Matthias Sohn2010-09-161-24/+24
| | | | | Change-Id: Ideab4923a5d8055f0e8a36ddcf0bc8adbf71c329 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* JGit 0.9.3v0.9.3Matthias Sohn2010-09-161-24/+24
| | | | | Change-Id: I114106f3286c36f7d5e136748a7e5130f4da163f Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* Qualify post-0.9.1 buildsMatthias Sohn2010-09-151-24/+24
| | | | | Change-Id: I07a3391de03379f32ecfd055d45750e3860b2be4 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* JGit 0.9.1v0.9.1Matthias Sohn2010-09-151-24/+24
| | | | | Change-Id: Ic411b1b8a7e6039ae3ff567e2c9cdd5db84f4d41 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* Refactor Git API exceptions to a new packageChris Aniszczyk2010-09-011-1/+2
| | | | | | | | Create a new 'org.eclipse.jgit.api.errors' package to contain exceptions related to using the Git porcelain API. Change-Id: Iac1781bd74fbd520dffac9d347616c3334994470 Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com>
* Merge branch 'delta'Shawn O. Pearce2010-07-221-0/+3
|\ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * delta: (103 commits) Discard the uncompressed delta as soon as its compressed Honor pack.windowlimit to cap memory usage during packing Honor pack.threads and perform delta search in parallel Cache small deltas during packing Implement delta generation during packing debug-show-packdelta: Dump a pack delta to the console Initial pack format delta generator Add debugging toString() method to ObjectToPack Make ObjectToPack clearReuseAsIs signal available to subclasses Correctly classify the compressing objects phase Refactor ObjectToPack's delta depth setting Configure core.bigFileThreshold into PackWriter Add doNotDelta flag to ObjectToPack Add more configuration options to PackWriter Save object path hash codes during packing Add path hash code to ObjectWalk Add getObjectSize to ObjectReader Allow TemporaryBuffer.Heap to allocate smaller than 8 KiB Define a constant for 127 in DeltaEncoder Cap delta copy instructions at 64k ... Conflicts: org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java Change-Id: I7c7a05e443a48d32c836173a409ee7d340c70796
| * Move PackWriter over to storage.pack.PackWriterShawn O. Pearce2010-06-261-0/+1
| | | | | | | | | | | | | | | | | | Similar to what we did with the file code, move the pack writer into its own package so the related classes and their package private methods are hidden from the rest of the library. Change-Id: Ic1b5c7c8c8d266e90c910d8d68dfc8e93586854f Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
| * Move FileRepository to storage.file.FileRepositoryShawn O. Pearce2010-06-261-0/+1
| | | | | | | | | | | | | | | | | | | | | | | | This move isolates all of the local file specific implementation code into a single package, where their package-private methods and support classes are properly hidden away from the rest of the core library. Because of the sheer number of files impacted, I have limited this change to only the renames and the updated imports. Change-Id: Icca4884e1a418f83f8b617d0c4c78b73d8a4bd17 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
| * Redo event listeners to be more genericShawn O. Pearce2010-06-251-0/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Replace the old crude event listener system with a much more generic implementation, patterned after the event dispatch techniques used in Google Web Toolkit 1.5 and later. Each event delivers to an interface that defines a single method, and the event itself is what performs the delivery in a type-safe way through its own dispatch method. Listeners are registered in a generic listener list, indexed by the interface they implement and wish to receive an event for. Delivery of events is performed by looping through all listeners implementing the event's corresponding listener interface, and using the event's own dispatch method to deliver the event. This is the classical "double dispatch" pattern for event delivery. Listeners can be unregistered by invoking remove() on their registration handle. This change therefore requires application code to track the handle if it wishes to remove the listener at a later point in time. Event delivery is now exposed as a generic public method on the Repository class, making it easier for any type of message to be sent out to any type of listener that has registered, without needing to pre-arrange for type-safe fireFoo() methods. New event types can be added in the future simply by defining a new RepositoryEvent subclass and a corresponding RepositoryListener interface that it dispatches to. By always adding new events through a new interface, we never need to worry about defining an Adapter to provide default no-op implementations of new event methods. Change-Id: I651417b3098b9afc93d91085e9f0b2265df8fc81 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
* | Add compatibility with gitignore specificationsCharley Wang2010-07-131-0/+1
|/ | | | | | | | | | | | | | | This patch adds ignore compatibility to jgit. It encompasses exclude files as well as .gitignore. Uses TreeWalk and FileTreeIterator to find nodes and parses .gitignore files when required. The patch includes a simple cache that can be used to save results and avoid excessive gitignore parsing. CQ: 4302 Bug: 303925 Change-Id: Iebd7e5bb534accca4bf00d25bbc1f561d7cad11b Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com> Signed-off-by: Stefan Lay <stefan.lay@sap.com> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
* Start 0.9 developmentShawn O. Pearce2010-06-141-19/+19
| | | | | Change-Id: I84173ece5100f1fcb78168e2e102b649d9466c08 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
* Qualify post-0.8.1 buildsShawn O. Pearce2010-06-021-19/+19
| | | | | Change-Id: Id86e5876b2f684b2a272c07061a276b054ba410d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
* JGit 0.8.1v0.8.1Shawn O. Pearce2010-06-021-19/+19
| | | | | Change-Id: I3d4ac7d0617a3575019e2ed748ed2a298a988340 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
* Externalize strings from JGitSasa Zivkov2010-05-191-1/+2
| | | | | | | | | | | | | | | The strings are externalized into the root resource bundles. The resource bundles are stored under the new "resources" source folder to get proper maven build. Strings from tests are, in general, not externalized. Only in cases where it was necessary to make the test pass the strings were externalized. This was typically necessary in cases where e.getMessage() was used in assert and the exception message was slightly changed due to reuse of the externalized strings. Change-Id: Ic0f29c80b9a54fcec8320d8539a3e112852a1f7b Signed-off-by: Sasa Zivkov <sasa.zivkov@sap.com>
* Add builder-style API to jgit and Commit & Log cmdChristian Halstrick2010-05-101-1/+2
| | | | | | | | | | | | | | | | | Added a new package org.eclipse.jgit.api and a builder-style API for jgit. Added also the first implementation for two git commands: Commit and Log. This API is intended to be used by external components when functionalities of the standard git commands are required. It will also help to ease writing JGit tests. For internal usages this API may often not be optimal because the git commands are doing much more than required or they expect parameters of an unappropriate type. Change-Id: I71ac4839ab9d2f848307eba9252090c586b4146b Signed-off-by: Christian Halstrick <christian.halstrick@sap.com>
* JGit plugin not compatible with Eclipse 3.4Robin Rosenberg2010-04-051-1/+1
| | | | | | | | | | | | | | | The JSch bundle in Eclipse 3.4 does not export its packages with version numbers. Use Require-Bundle on version 0.1.37 that comes with Eclipse 3.4 There is no 0.1.37 in the maven repositories so the pom still refers to 0.1.41 so the build can get the compile time dependencies right. Bug: 308031 CQ: 3904 jsch Version: 0.1.37 (using Orbit CQ2014) Change-Id: I12eba86bfbe584560c213882ebba58bf1f9fa0c1 Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
* Qualify builds as 0.8.0Shawn O. Pearce2010-03-201-17/+17
| | | | | | | | | Since the API is changing relative to 0.7.0, we'll call our next release 0.8.1. But until that gets released, builds from master will be 0.8.0.qualifier. Change-Id: I921e984f51ce498610c09e0db21be72a533fee88 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>