public boolean undo(boolean redoable) throws IOException {
		synchronized (snapshotLock) {
			synchronized (this) {

				if (lockCount != 0) {
					throw new AssertException(
						"Can't undo with locked buffers (" + lockCount + " locks found)");
				}

				int ix = checkpointHeads.size() - 1;
				if (ix < 1) {
					return false;
				}

				modifiedSinceSnapshot = true;

				// Remove current checkpoint
				BufferNode cpHead = checkpointHeads.remove(ix);

				// Process all nodes within the checkpoint list
				BufferNode node = cpHead.nextInCheckpoint;
				int srcIndexCnt = sourceFile != null ? sourceFile.getIndexCount() : 0;
				int revisedIndexCnt = indexProvider.getIndexCount();
				while (node.id != TAIL) {

					BufferNode oldVer = node.nextVersion;
					node.removeFromVersion();

					if (oldVer.prevVersion.id != HEAD) {
						throw new AssertException(); // ?? should never happen
					}

					if (oldVer.id == TAIL) {
						// Node was unknown prior to this checkpoint
						bufferTable.remove(node.id);
						if (sourceFile == null || node.id >= srcIndexCnt) {
							// Buffer was created within checkpoint - compute file truncation index
							revisedIndexCnt = Math.min(node.id, revisedIndexCnt);
						}
						else if (!node.empty) {
							// Buffer was changed from original source - free it
							indexProvider.freeIndex(node.id);
						}
					}
					else {
						if (node.empty) {
							// Node was removed within checkpoint - reallocate if appropriate
							// Double check previous checkpoint - make sure it is not empty
							if (!oldVer.empty) {
								if (!indexProvider.allocateIndex(node.id)) {
									throw new AssertException();
								}
							}
						}
						else if (oldVer.empty) {
							// Node was re-allocated within this checkpoint
							indexProvider.freeIndex(node.id);
						}
						oldVer.clearSnapshotTaken();
					}
					node = node.nextInCheckpoint;
				}
				indexProvider.truncate(revisedIndexCnt);

				if (redoable) {
					// Move checkpoint to redo list
					redoCheckpointHeads.add(cpHead);
				}
				else {
					// Elliminate checkpoint contents and redo stack
					// if not redoable
					disposeNodeList(cpHead);
					disposeRedoCheckpoints();
				}

				// Set current checkpoint
				cpHead = checkpointHeads.get(ix - 1);
				currentCheckpoint = cpHead.checkpoint;
				currentCheckpointHead = null;

				return true;
			}
		}
	}
Backup to previous checkpoint.  Method should not be invoked
when one or more buffers are locked.
@param redoable true if currrent checkpoint should be moved to redo stack
@return true if successful else false
@throws IOException if IO error occurs
Ghidra/Framework/DB/src/main/java/db/buffers/BufferMgr.java