Giter VIP home page Giter VIP logo

rudikershaw / git-build-hook Goto Github PK

View Code? Open in Web Editor NEW
124.0 4.0 19.0 340 KB

A maven plugin for managing client side (local) git configuration for those working on your project. Including but not limited to setting git config, installing hooks, validating the local repository.

License: MIT License

Java 99.25% Shell 0.75%
git hooks git-hooks git-hook maven maven-plugin build project-management installer client-git-hooks

git-build-hook's Introduction

The Git Build Hook Maven Plugin Logo

MIT Licence Build Status Maven Central

Git Build Hook Maven Plugin

A Maven plugin used to add configuration, install git hooks, and initialize the local project's git repository. It is common for a team or project to need to manage client side git configuration. For example, you may need to install pre-commit hooks for all your developers, or insist on a particular core.autoclrf policy. This plugin allows you to setup configuration for every developer working on the project the first time they run your build.

Key Features

  • Set arbitrary project specific git configuration.
  • Install client side (local) git hooks for the project.
  • Fail the build if your project is not being managed by Git.
  • Use with Maven archetypes to initialise Git repository with the first build.

Basic Usage

A common use-case might be to install local git hooks by setting the core.hooksPath configuration. Put all your Git hooks in a directory in your project, then configure your pom.xml to include the following plugin declaration, goal, and configuration.

<build>
  <plugins>
    <plugin>
      <groupId>com.rudikershaw.gitbuildhook</groupId>
      <artifactId>git-build-hook-maven-plugin</artifactId>
      <version>3.4.1</version>
      <configuration>
        <gitConfig>
          <!-- The location of the directory you are using to store the Git hooks in your project. -->
          <core.hooksPath>hooks-directory/</core.hooksPath>
          <!-- Some other project specific git config that you want to set. -->
          <custom.configuration>true</custom.configuration> 
        </gitConfig>
      </configuration>
      <executions>
        <execution>
          <goals>       
            <!-- Sets git config specified under configuration > gitConfig. -->
            <goal>configure</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
      <!-- ... etc ... -->
  </plugins>
</build>

When you run your project build the plugin will configure git to run hooks out of the directory specified. This will effectively set up the hooks in that directory for everyone working on your project. If you would prefer to install individual git hooks into the default hooks directory, then you can use the install goal with configuration for each hook you wish to install like so;

...
      <configuration>
        <installHooks>
          <!-- The location of a git hook to install into the default hooks directory. -->
          <pre-commit>file_path/to/your/hook.sh</pre-commit>
          <commit-msg>class_path/package/hook.sh</commit-msg>
        </installHooks>
      </configuration>
      <dependencies>
        <dependency>
          <groupId>my.company</groupId>
          <artifactId>company-git-hooks</artifactId>
          <version>[1.2.3,)</version>
        <dependency>
      </dependencies>
...
      <goals>       
        <!-- Install specific hooks directly to the default hooks directory. -->
        <goal>install</goal>
      </goals>
...

With both of the above goals, the build will fail if the project is not managed by Git. If you would prefer the plugin to, instead of failing, initialize a new Git repository at the root of the project you can do the following;

...
<goals>       
  <!-- Initialize a Git repository at the root of the project if one does not exist. -->
  <goal>initialize</goal>
  <goal>configure</goal>
</goals>
...

Wait, but why?

Many web-based hosting services for version control using Git, do not allow server side hooks. Server side hooks are extremely useful for enforcing certain styles of commit message, restricting the kind and types of actions that can be performed against certain branches, providing useful feedback or advice during certain actions in Git, and much more. This kind of quick feedback is advantageous when managing any large group of developers.

If you cannot perform these kind of actions server side, what else can be done? Well, the hooks can be installed on the developers local machines. But it can be difficult to organise groups of people to install these hooks and even more difficult to get updates out to everyone.

If only there was some way that the hooks could be managed in your project repository and installed automatically during your build. Well, that is what this plugin is for.

git-build-hook's People

Contributors

david4096 avatar dependabot[bot] avatar etzelc avatar gabrielczar avatar lukewhitt avatar mendred avatar nhojpatrick avatar rudikershaw avatar summersb avatar themadprofessor avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

git-build-hook's Issues

Plugin throws FileAlreadyExistsException if git hook script already exists

I've installed the plugin on a project and it is working wonderfully. However, when running mvn clean install twice in a row on the project, the following is printed in the build output:

[WARNING] Could not move file into .git/hooks directory
java.nio.file.FileAlreadyExistsException: <path>/.git/hooks/commit-msg
    at sun.nio.fs.UnixCopyFile.copy (UnixCopyFile.java:573)
    at sun.nio.fs.UnixFileSystemProvider.copy (UnixFileSystemProvider.java:254)
    at java.nio.file.Files.copy (Files.java:1294)
    at com.rudikershaw.gitbuildhook.InstallMojo.installGitHook (InstallMojo.java:102)
    at com.rudikershaw.gitbuildhook.InstallMojo.execute (InstallMojo.java:86)
    at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo (DefaultBuildPluginManager.java:137)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:208)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:154)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:146)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:117)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:81)
    at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build (SingleThreadedBuilder.java:56)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute (LifecycleStarter.java:128)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:305)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:192)
    at org.apache.maven.DefaultMaven.execute (DefaultMaven.java:105)
    at org.apache.maven.cli.MavenCli.execute (MavenCli.java:954)
    at org.apache.maven.cli.MavenCli.doMain (MavenCli.java:288)
    at org.apache.maven.cli.MavenCli.main (MavenCli.java:192)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (Native Method)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62)
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke (Method.java:566)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced (Launcher.java:289)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch (Launcher.java:229)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode (Launcher.java:415)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main (Launcher.java:356)

Add a skip option to the Mojos

It would be nice to have a skip option on both Mojos.

We have a Maven archetype for a Maven project that uses this git hooks plugin, but the Maven archetype project itself does not use the git hooks plugin. The archetype project however runs integrations tests via archetype:integration-test. Since the archetype integration test plugin creates a temporary git project in target, and the archetype project already has an initialized .git folder, when the test Maven project is built, it creates a git hook in the archetype project. This hook then tries to run spotless, which doesn't exist in the archetype project.

A skip option would help to skip the git hooks plugin when running IT tests on the archetype project.

Use Pre-Commit Hooks only?

Hi,

I discovered this project today and wanted to say thank you. I try to configure this for my team but we only need a pre-commit hook todo some formatting before commiting.
It seems to still run all the hook even if I only specify the pre-commit hook.
Am I missing something?

<plugin>
        <groupId>com.rudikershaw.gitbuildhook</groupId>
        <artifactId>git-build-hook-maven-plugin</artifactId>
        <version>3.0.0</version>
        <configuration>
          <installHooks>
            <pre-commit>etc/git-hooks/pre-commit-hooks/format.sh</pre-commit>
          </installHooks>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>install</goal>
            </goals>
          </execution>
        </executions>
</plugin>

gitconfig add vs set

I was testing out a way to use git notes to maintain a changelog (https://dev.to/leehambley/effortlessly-maintain-a-high-quality-change-log-with-git-notes-4bm5)

So I used the plugin to set

<remote.origin.fetch>+refs/notes/changelog:refs/notes/changelog</remote.origin.fetch>

But this overwrote the default

remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*

So I need an implementation of

git config [<file-option>] [--type=<type>] --add name value

Instead of

git config [<file-option>] [--type=<type>] [--fixed-value] [--show-origin] [--show-scope] [-z|--null] name [value [value-pattern]]

Maybe it could be done like this:

<remote.origin.fetch add="">+refs/notes/changelog:refs/notes/changelog</remote.origin.fetch>

NPE when using version 3.4.1

when i configured like

            <plugin>
                <groupId>com.rudikershaw.gitbuildhook</groupId>
                <artifactId>git-build-hook-maven-plugin</artifactId>
                <version>3.4.1</version>
                <configuration>
                    <installHooks>
                        <pre-commit>./hooks/pre-commit.sh</pre-commit>
                    </installHooks>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>configure</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

I got NPE

Caused by: org.apache.maven.plugin.PluginExecutionException: Execution default of goal com.rudikershaw.gitbuildhook:git-build-hook-maven-plugin:3.4.1:configure failed.
    at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo (DefaultBuildPluginManager.java:148)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:210)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:156)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:148)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:117)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:81)
    at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build (SingleThreadedBuilder.java:56)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute (LifecycleStarter.java:128)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:305)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:192)
    at org.apache.maven.DefaultMaven.execute (DefaultMaven.java:105)
    at org.apache.maven.cli.MavenCli.execute (MavenCli.java:957)
    at org.apache.maven.cli.MavenCli.doMain (MavenCli.java:289)
    at org.apache.maven.cli.MavenCli.main (MavenCli.java:193)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (Native Method)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62)
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke (Method.java:566)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced (Launcher.java:282)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch (Launcher.java:225)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode (Launcher.java:406)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main (Launcher.java:347)
    at org.codehaus.classworlds.Launcher.main (Launcher.java:47)
Caused by: java.lang.NullPointerException
    at com.rudikershaw.gitbuildhook.GitConfigMojo.execute (GitConfigMojo.java:49)
    at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo (DefaultBuildPluginManager.java:137)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:210)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:156)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:148)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:117)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:81)
    at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build (SingleThreadedBuilder.java:56)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute (LifecycleStarter.java:128)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:305)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:192)
    at org.apache.maven.DefaultMaven.execute (DefaultMaven.java:105)
    at org.apache.maven.cli.MavenCli.execute (MavenCli.java:957)
    at org.apache.maven.cli.MavenCli.doMain (MavenCli.java:289)
    at org.apache.maven.cli.MavenCli.main (MavenCli.java:193)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (Native Method)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62)
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke (Method.java:566)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced (Launcher.java:282)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch (Launcher.java:225)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode (Launcher.java:406)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main (Launcher.java:347)
    at org.codehaus.classworlds.Launcher.main (Launcher.java:47)
[ERROR] 
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/PluginExecutionException

Process finished with exit code 1

This is caused by GitConfigMojo#gitConfig could be null.

Please consider fixing it.

Disable on CI

Hi!
There are some cases when this plugin needs to be disabled, for example, in continuous integration environment that does a git shallow clone and there is no need to configure any hook.

Maybe an environment variable that if is present the plugin won't run?

Regards!

Allow install goal to pull down hook from remote URIs

Possibly worth thinking about letting the install hook take arbitrary URIs from the configuration so that you can get your hooks from more or less anywhere.

Also worth thinking about the security implications of allowing users to download arbitrary files, set them to executable, and then having them run when they commit etc.

Better error messages when declaring invalid configuration

In the following plugin declaration, the configure goal is used but no configuration is declared. This results in a NullPointerException. This is confusing, and does not provide the API user with any guidance on how to proceed.

<plugin>
    <groupId>com.rudikershaw.gitbuildhook</groupId>
    <artifactId>git-build-hook-maven-plugin</artifactId>
    <version>3.4.1</version>
    <executions>
        <execution>
            <goals>
                <goal>configure</goal>
            </goals>
        </execution>
    </executions>
</plugin>

The plugin should throw a MojoFailureException with a helpful message that instructs the API user to declare appropriate configuration for the goal.

Plugin validation issues

Hi there,
thank you again for creating this great plugin :)

I recently ran Maven Plugin Validation on my projects, and it warned me about multiple validation issues. This is the message that I got:

[WARNING]  * com.rudikershaw.gitbuildhook:git-build-hook-maven-plugin:3.4.1
[WARNING]   Plugin EXTERNAL issue(s):
[WARNING]    * Plugin is a Maven 2.x plugin, which will be not supported in Maven 4.x
[WARNING]    * Plugin mixes multiple Maven versions: [3.9.0, 2.2.1]

Maven setup:

Apache Maven 3.9.3 (21122926829f1ead511c958d89bd2f672198ae9f)
Maven home: C:\apache-maven-3.8.6-bin\apache-maven-3.8.6
Java version: 19.0.2, vendor: Eclipse Adoptium, runtime: C:\Program Files\Eclipse Adoptium\jdk-19.0.2.7-hotspot
Default locale: de_DE, platform encoding: UTF-8
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

To run the plugin validation yourself, you need a project that uses your plugin and then run mvn -Dmaven.plugin.validation=verbose package which will run a build and show all plugin validation errors at the end.

This is more of a warning rather than an actual error, so fixing it has a rather low priority, but I wanted to let you know nevertheless.

Cheers :)

Validate Git version for the configure goal

The configure goal works by using a feature of Git 2.9+, and so this goal should validate that the version of Git installed is 2.9 or greater. If not, the goal should fail the build.

Thread safety when setting hooksPath

Hi there,
I am using your plugin to set the directory for git hooks (c.f. my configuration below). Similar to #31 , I am trying to run a multi-threaded multi-module build with the -T 1C flag in maven. Even though the configure goal is marked as thread safe, it still fails sometimes with the following error:

Failed to execute goal com.rudikershaw.gitbuildhook:git-build-hook-maven-plugin:3.2.0:configure (install-git-hooks) on project image-converter.test-utils: Could not find or initialise a local git repository. A repository is required.: C:\Users\kammel\git\image-converter\.git\config (Der Prozess kann nicht auf die Datei zugreifen, da sie von einem anderen Prozess verwendet wird) -> [Help 1]

Essentially, this means that git-build-hook-maven-plugin is running in two different modules at the same time, trying to modify .git\config. Since one thread locks the file, the second thread fails to get write access, failing the build. The plugin would therefore need to synchronize write access to the config file to avoid that.

Here's my configuration:

pom.xml:

<plugin>
    <groupId>com.rudikershaw.gitbuildhook</groupId>
    <artifactId>git-build-hook-maven-plugin</artifactId>
    <version>3.2.0</version>
    <configuration>
        <gitConfig>
            <core.hooksPath>git-hooks/</core.hooksPath>
        </gitConfig>
    </configuration>
    <executions>
        <execution>
            <id>install-git-hooks</id>
            <goals>
                <goal>configure</goal>
            </goals>
            <phase>generate-sources</phase>
        </execution>
    </executions>
</plugin>

System configuration:
Maven home: C:\apache-maven-3.8.6-bin\apache-maven-3.8.6
Java version: 17.0.4.1, vendor: Eclipse Adoptium, runtime: C:\Program Files\Eclipse Adoptium\jdk-17.0.4.101-hotspot
Default locale: de_DE, platform encoding: Cp1252
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

I'm happy to help if you need any further information.
Cheers and thanks for developing the plugin ;)

Allow users to set/add arbitrary git configuration

Adding a goal to set git config will let people ensure autoclrf is set a certain way, or turn on and off (or adjust) git hooks (which are typically enabled or disabled using arbitrary git config settings).

Git worktree support

The plugin fails to detect the repository when in a worktree:

git worktree add ../newworktree
mvn -f ../newworktree

Could not find or initialise a local git repository. A repository is required.

Update README.md for 3.0.0 to reflect new API

After the changes in 3.0.0, the README will need some significant changes to reflect the increase of scope of the plugin. Additionally, configuration for the plugins goals has been changed. This will also need updating in the README.md

Tests are run against latest released version

The tests in the project are being run against the latest released version of the plugin, not the version that has just been built.

This is obviously a problem. Importantly it is an especially big problem for the unreleased/2.0.0 branch that has some fairly significant changes that will not pass testing until this has been resolved.

Fails to initialize repo if using worktrees on windows

When running mvn compile I receive the following error

[ERROR] Failed to execute goal com.rudikershaw.gitbuildhook:git-build-hook-maven-plugin:3.4.1:configure (default) on project recap-parent: Could not find or initialise a local git repository. A repository is required.: repository not found: {mypath}\repo.git\worktrees{branchname} -> [Help 1]

threadsafe

Pls mark as @threadsafe to support parallel building to avoid this warning

[WARNING] *****************************************************************                                                                                                                                   [WARNING] * Your build is requesting parallel execution, but project      *                                                                                                                                   
[WARNING] * contains the following plugin(s) that have goals not marked   *                                                                                                                                   
[WARNING] * as @threadSafe to support parallel building.                  *                                                                                                                                   
[WARNING] * While this /may/ work fine, please look for plugin updates    *                                                                                                                                   
[WARNING] * and/or request plugins be made thread-safe.                   *                                                                                                                                   
[WARNING] * If reporting an issue, report it against the plugin in        *
[WARNING] * question, not against maven-core                              *
[WARNING] *****************************************************************
[WARNING] The following plugins are not marked @threadSafe in Traderoot Maven Super:
[WARNING] com.rudikershaw.gitbuildhook:git-build-hook-maven-plugin:3.1.0
[WARNING] Enable debug to see more precisely which goals are not marked @threadSafe.
[WARNING] *****************************************************************

Goal 'check' is badly named

The goal 'check' does the initial check to see if the git repository exists and fails if it does not, it also initialises a repo if one does not already exist, and then installs the specified hooks.

Replace the goal 'check' with two goals. 'initialize' and 'install' where the first initialises the repo it does not already exist, and the second installs the hooks. That way we can have separate mojos for each goal. Single responsibility per file.

Additionally, make the default phase for the new initialize goal the Maven initialize phase. And move the default phase of the install goal to the Maven generate-sources phase.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.