 |
NetBeans? Version Control System Integration Guide
This document describes how to implement and register version control system provider that can be used by other IDE components (projects, nodes, editors and filesystems).
There is sibling ProjectVersioning that describes how to code version control aware IDE componets.
Document Status
The document describes basic integration. More detailed tricks are TODO.
Goal
This document describes version control integration on a virtual NoVersion version control system that keeps local history.
Integration Points Summary
Version control system should integrate with IDE filesystems, IDE project system, IDE diff, IDE editor, IDE window system.
More advanced integrations are possible, but not described here. Check particular module APIs.
Your project should declare dependencies on:
- org.netbeans.api.progress
- Progress UI and cancel
- org.netbeans.modules.diff
- Differences engine and UI, patch algorithm and conflict resolver UI
- org.netbeans.modules.editor,
org.netbeans.modules.editor.errorstripe, org.netbeans.modules.editor.fold, org.netbeans.modules.editor.lib, org.netbeans.modules.editor.mimelookup, org.netbeans.modules.editor.settings - Editor sidebars
- org.netbeans.modules.masterfs,
org.openide.filesystems - Project nodes annotations, context actions, FS operations intercaption
- org.netbeans.modules.projectapi,
org.netbeans.modules.projectuiapi - Project level integration
- org.netbeans.modules.queries
- ignore list default (SharabilityQuery)
- org.netbeans.modules.versioning
- SOCKS proxy, Turbo cache
- org.openide.io
- Output window
- org.openide.windows
- Main menu definition, windows (TopComponent)
- OpenIDE APIs
- Nodes, DataObjects, Lookup, ModuleInstall?
- org.openide.awt
- org.openide.dialogs
- org.openide.explorer
- org.openide.loaders
- org.openide.modules
- org.openide.nodes
- org.openide.options
- org.openide.text
- org.openide.util
Create NetBeans? Plug-in Project
From File menu select New Project... and choose NetBeans Plug-in Module, Module Project.
It creates blank project with (under Important files) XML Layer and Module Manifest. From project contextual menu select Properties and in Libraries section declare all above dependencies.
Main Menu Actions
Open module XML Layer and define user actions. A user reachable action is a SystemAction? subclass that is registered in Actions repository an d potentially in Main menu and have assigned a shortcut.
Example action:
package org.noversion;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.nodes.Node;
import org.openide.util.HelpCtx;
import org.openide.util.RequestProcessor;
import org.openide.util.actions.NodeAction;
/**
* Sample action
*/
public class NoVersionAction extends NodeAction {
public NoVersionAction() {
setIcon(null);
putValue("noIconInMenu", Boolean.TRUE); // NOI18N
}
protected void performAction(final Node[] node) {
// do all necessary node queries getting files it represents
// you can inspire in org.netbeans.subversion.util.SvnUtils.getCurrentContext()
// @see VersionActionContext link bellow
RequestProcessor.getDefault().post(new Runnable() {
public void run() {
ProgressHandle progress =
ProgressHandleFactory.createHandle("No-versioning...");
progress.start();
try {
// NoVersion.perform();
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
}
} finally {
progress.finish();
}
}
});
}
protected boolean enable(Node[] node) {
return true;
}
public String getName() {
return "No Version";
}
protected boolean asynchronous() {
return false; // => performAction comes from AWT
}
public HelpCtx getHelpCtx() {
return new HelpCtx(getClass());
}
}
Note: See typical VersionActionContext implementation (Node[] => File[] transformation).
Sample XML Layer registration snippet:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.1//EN" "http://www.netbeans.org/dtds/filesystem-1_1.dtd">
<filesystem>
<!-- Actions Registry -->
<folder name="Actions">
<folder name="Noversion">
<file name="org-noversion-NoVersionAction.instance">
<attr name="instanceClass" stringvalue="org.noversion.NoVersionAction"/>
</file>
</folder>
</folder>
<!-- Main Menu Reference -->
<folder name="Menu">
<!-- Menu Location after Run, CVS, Subversion, Refactoring but before Tools -->
<attr name="RunProject/NoVersion" boolvalue="true"/>
<attr name="Refactoring/NoVersion" boolvalue="true"/>
<attr name="CVS/NoVersion" boolvalue="true"/>
<attr name="SVN/NoVersion" boolvalue="true"/>
<attr name="NoVersion/Tools" boolvalue="true"/>
<folder name="NoVersion">
<attr name="SystemFileSystem.localizingBundle" stringvalue="org.noversion.Bundle"/>
<file name="org-noversion-NoVersionAction.shadow">
<attr name="originalFile" stringvalue="Actions/Noversion/org-noversion-NoVersionAction.instance"/>
</file>
</folder>
</folder>
<!-- Keyboard Shortcut Reference -->
<folder name="Shortcuts">
<file name="C-Q.shadow">
<attr name="originalFile" stringvalue="Actions/Noversion/org-noversion-NoVersionAction.instance"/>
</file>
</folder>
</filesystem>
Relevant Bundle.properties:
OpenIDE-Module-Name=NoVersion
Menu/Noversion=N&o.Version
Check your results, from project context menu choose Install/Reload in Target Platform, there should be new menu item.
Actual NoVersionAction action business logic is upon you.
Project Explorer Contextual Actions and Node Badging
Node contextual actions and their badging are delivered using MasterFileSystem's AnnotationProvider.
Subclass AnnottaionProvider and register it as a service in your META-INF/services.
Note: MasterFileSystem is non-API module you need establish implementation dependency or become it's friend,
Create META-INF/services/org.netbeans.modules.masterfs.providers.AnnotationProvider file that reads:
org.noversion.FileStatusProvider
and its example implementation:
package org.noversion;
import java.awt.Image;
import java.io.*;
import java.util.*;
import java.util.Set;
import javax.swing.Action;
import org.netbeans.modules.masterfs.providers.AnnotationProvider;
import org.netbeans.modules.masterfs.providers.InterceptionListener;
import org.openide.filesystems.*;
/**
* Recognizes noversioned work dirs, adds context action
* and annotates node name.
*/
public class FileStatusProvider extends AnnotationProvider {
/** Creates a new instance of FileStatusProvider */
public FileStatusProvider() {
}
public String annotateName(String name, Set files) {
if (isManaged(files)) {
// XXX too simplified
Iterator it = files.iterator();
if (it.hasNext()) {
FileObject first = (FileObject) it.next();
String version = getNoVersion(FileUtil.toFile(first));
if (version != null) {
return name + " [" + version + "]";
}
}
}
return null;
}
public Image annotateIcon(Image image, int i, Set set) {
return null;
}
public String annotateNameHtml(String name, Set files) {
if (isManaged(files)) {
// XXX too simplified. add HTML escaping, etc
Iterator it = files.iterator();
if (it.hasNext()) {
FileObject first = (FileObject) it.next();
String version = getNoVersion(FileUtil.toFile(first));
if (version != null) {
return "<html><font color=\"#0000FF\">" + name + "</font> <i>" + version + "</i>";
}
}
}
return null;
}
public Action[] actions(Set files) {
if (isManaged(files)) {
// FIXME add submenu example
}
return null;
}
public InterceptionListener getInterceptionListener() {
return null;
}
/**
* @return true if at least one file is managed (any parent
* has <tt>noversion.properties</tt> and it is not explicitly marked
* as unmanaged (future user action feature))
*/
private static boolean isManaged(Set fileObjects) {
boolean managed = false;
Iterator it = fileObjects.iterator();
while (it.hasNext()) {
FileObject fo = (FileObject) it.next();
File file = FileUtil.toFile(fo);
if (file == null) {
continue;
}
return getMarker(file) != null;
}
return false;
}
private static File getMarker(File file) {
File parent = file.getParentFile();
if (parent != null) {
File marker = new File(parent, "noversion.properties");
if (marker.isFile()) {
return marker;
}
}
return null;
}
private static String getNoVersion(File file) {
if (file == null) {
return null;
}
File props = getMarker(file);
if (props != null) {
Properties map = new Properties();
try {
map.load(new FileInputStream(props));
return map.getProperty(file.getName());
} catch (IOException ex) {
// FIXME ignore
}
}
return null;
}
}
Project Contextual Menu
Project node uses TODO registration mechanism.
Diff
TODO call Diff APIs.
Editor
TODO Editor sidebar resgistration.
Downloads
TODO How to attach NoVersion module zip archive?
Idea: Create noversion project on java.net.
Other Documentation
-- Main.pkuzel - 28 Mar 2006
|