Add git_repository and new_git_repository workspace rules.

TESTED=Added integration tests.

--
MOS_MIGRATED_REVID=98396197
diff --git a/src/main/java/BUILD b/src/main/java/BUILD
index 45b6cf8..a8252ab 100644
--- a/src/main/java/BUILD
+++ b/src/main/java/BUILD
@@ -454,11 +454,13 @@
         "//third_party:apache_commons_pool2",
         "//third_party:auto_value",
         "//third_party:guava",
+        "//third_party:jgit",
         "//third_party:joda_time",
         "//third_party:jsr305",
         "//third_party:jsr330_inject",
         "//third_party:maven_model",
         "//third_party:protobuf",
+        "//third_party:slf4j",
     ],
 )
 
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
index 7e3265b..555f0cb 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
@@ -22,12 +22,15 @@
 import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
 import com.google.devtools.build.lib.analysis.RuleDefinition;
 import com.google.devtools.build.lib.bazel.commands.FetchCommand;
+import com.google.devtools.build.lib.bazel.repository.GitCloneFunction;
+import com.google.devtools.build.lib.bazel.repository.GitRepositoryFunction;
 import com.google.devtools.build.lib.bazel.repository.HttpArchiveFunction;
 import com.google.devtools.build.lib.bazel.repository.HttpDownloadFunction;
 import com.google.devtools.build.lib.bazel.repository.HttpJarFunction;
 import com.google.devtools.build.lib.bazel.repository.JarFunction;
 import com.google.devtools.build.lib.bazel.repository.LocalRepositoryFunction;
 import com.google.devtools.build.lib.bazel.repository.MavenJarFunction;
+import com.google.devtools.build.lib.bazel.repository.NewGitRepositoryFunction;
 import com.google.devtools.build.lib.bazel.repository.NewHttpArchiveFunction;
 import com.google.devtools.build.lib.bazel.repository.NewLocalRepositoryFunction;
 import com.google.devtools.build.lib.bazel.repository.RepositoryDelegatorFunction;
@@ -42,10 +45,12 @@
 import com.google.devtools.build.lib.bazel.rules.android.AndroidRepositoryRules.AndroidHttpToolsRepositoryRule;
 import com.google.devtools.build.lib.bazel.rules.android.AndroidSdkRepositoryFunction;
 import com.google.devtools.build.lib.bazel.rules.android.AndroidSdkRepositoryRule;
+import com.google.devtools.build.lib.bazel.rules.workspace.GitRepositoryRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.HttpArchiveRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.HttpJarRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.LocalRepositoryRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.MavenJarRule;
+import com.google.devtools.build.lib.bazel.rules.workspace.NewGitRepositoryRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.NewHttpArchiveRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.NewLocalRepositoryRule;
 import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
@@ -75,14 +80,17 @@
   private final ImmutableMap<String, RepositoryFunction> repositoryHandlers;
   private final AtomicBoolean isFetch = new AtomicBoolean(false);
   private HttpDownloadFunction downloadFunction;
+  private GitCloneFunction gitCloneFunction;
 
   public BazelRepositoryModule() {
     repositoryHandlers = ImmutableMap.<String, RepositoryFunction>builder()
         .put(LocalRepositoryRule.NAME, new LocalRepositoryFunction())
         .put(HttpArchiveRule.NAME, new HttpArchiveFunction())
+        .put(GitRepositoryRule.NAME, new GitRepositoryFunction())
         .put(HttpJarRule.NAME, new HttpJarFunction())
         .put(MavenJarRule.NAME, new MavenJarFunction())
         .put(NewHttpArchiveRule.NAME, new NewHttpArchiveFunction())
+        .put(NewGitRepositoryRule.NAME, new NewGitRepositoryFunction())
         .put(NewLocalRepositoryRule.NAME, new NewLocalRepositoryFunction())
         .put(AndroidSdkRepositoryRule.NAME, new AndroidSdkRepositoryFunction())
         .put(AndroidNdkRepositoryRule.NAME, new AndroidNdkRepositoryFunction())
@@ -96,6 +104,7 @@
   @Override
   public void beforeCommand(BlazeRuntime runtime, Command command) {
     downloadFunction.setReporter(runtime.getReporter());
+    gitCloneFunction.setReporter(runtime.getReporter());
   }
 
   @Override
@@ -154,6 +163,8 @@
     // Helper SkyFunctions.
     downloadFunction = new HttpDownloadFunction();
     builder.put(SkyFunctionName.create(HttpDownloadFunction.NAME), downloadFunction);
+    gitCloneFunction = new GitCloneFunction();
+    builder.put(SkyFunctionName.create(GitCloneFunction.NAME), gitCloneFunction);
     builder.put(JarFunction.NAME, new JarFunction());
     builder.put(ZipFunction.NAME, new ZipFunction());
     builder.put(TarGzFunction.NAME, new TarGzFunction());
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/GitCloneFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/GitCloneFunction.java
new file mode 100644
index 0000000..2c8e50a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/GitCloneFunction.java
@@ -0,0 +1,180 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.bazel.repository;
+
+import com.google.devtools.build.lib.bazel.repository.RepositoryFunction.RepositoryFunctionException;
+import com.google.devtools.build.lib.events.Reporter;
+import com.google.devtools.build.lib.packages.AggregatingAttributeMapper;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
+import com.google.devtools.build.skyframe.SkyFunctionName;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.InvalidRefNameException;
+import org.eclipse.jgit.api.errors.InvalidRemoteException;
+import org.eclipse.jgit.api.errors.RefNotFoundException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Objects;
+
+import javax.annotation.Nullable;
+
+/**
+ * Clones a Git repository, checks out the provided branch, tag, or commit, and
+ * clones submodules if specified.
+ */
+public class GitCloneFunction implements SkyFunction {
+  public static final String NAME = "GIT_CLONE";
+  private Reporter reporter;
+
+  public void setReporter(Reporter reporter) {
+    this.reporter = reporter;
+  }
+
+  @Nullable
+  @Override
+  public SkyValue compute(SkyKey skyKey, Environment env) throws RepositoryFunctionException {
+    GitRepositoryDescriptor descriptor = (GitRepositoryDescriptor) skyKey.argument();
+    String outputDirectory = descriptor.directory.toString();
+
+    Git git = null;
+    try {
+      git = Git.cloneRepository()
+          .setURI(descriptor.remote)
+          .setDirectory(new File(outputDirectory))
+          .setCloneSubmodules(false)
+          .setProgressMonitor(
+              new GitProgressMonitor("Cloning " + descriptor.remote, reporter))
+          .call();
+      git.checkout()
+          .setCreateBranch(true)
+          .setName("bazel-checkout")
+          .setStartPoint(descriptor.checkout)
+          .call();
+
+      // Using CloneCommand.setCloneSubmodules() results in SubmoduleInitCommand and
+      // SubmoduleUpdateCommand to be called recursively for all submodules. This is not
+      // desirable for repositories, such as github.com/rust-lang/rust-installer, which
+      // recursively includes itself as a submodule, which would result in an infinite
+      // loop if submodules are cloned recursively. For now, limit submodules to only
+      // the first level.
+      if (descriptor.initSubmodules) {
+        if (!git.submoduleInit().call().isEmpty()) {
+          git.submoduleUpdate()
+              .setProgressMonitor(
+                  new GitProgressMonitor("Cloning submodules for " + descriptor.remote, reporter))
+              .call();
+        }
+      }
+    } catch (InvalidRemoteException e) {
+      throw new RepositoryFunctionException(
+          new IOException("Invalid Git repository URI: " + e.getMessage()),
+          Transience.PERSISTENT);
+    } catch (RefNotFoundException|InvalidRefNameException e) {
+      throw new RepositoryFunctionException(
+          new IOException("Invalid branch, tag, or commit: " + e.getMessage()),
+          Transience.PERSISTENT);
+    } catch (GitAPIException e) {
+      throw new RepositoryFunctionException(
+          new IOException(e.getMessage()), Transience.TRANSIENT);
+    } finally {
+      if (git != null) {
+        git.close();
+      }
+    }
+    return new HttpDownloadValue(descriptor.directory);
+  }
+
+  @Nullable
+  @Override
+  public String extractTag(SkyKey skyKey) {
+    return null;
+  }
+
+  public static SkyKey key(Rule rule, Path outputDirectory)
+      throws RepositoryFunctionException {
+    AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule);
+    if ((mapper.has("commit", Type.STRING) == mapper.has("tag", Type.STRING))
+        && (mapper.get("commit", Type.STRING).isEmpty()
+            == mapper.get("tag", Type.STRING).isEmpty())) {
+      throw new RepositoryFunctionException(
+          new EvalException(rule.getLocation(), "One of either commit or tag must be defined"),
+          Transience.PERSISTENT);
+    }
+    String startingPoint;
+    if (mapper.has("commit", Type.STRING) && !mapper.get("commit", Type.STRING).isEmpty()) {
+      startingPoint = mapper.get("commit", Type.STRING);
+    } else {
+      startingPoint = "tags/" + mapper.get("tag", Type.STRING);
+    }
+
+    return new SkyKey(
+        SkyFunctionName.create(NAME),
+        new GitCloneFunction.GitRepositoryDescriptor(
+            mapper.get("remote", Type.STRING),
+            startingPoint,
+            mapper.get("init_submodules", Type.BOOLEAN),
+            outputDirectory));
+  }
+
+  static final class GitRepositoryDescriptor {
+    private String remote;
+    private String checkout;
+    private boolean initSubmodules;
+    private Path directory;
+
+    public GitRepositoryDescriptor(String remote, String checkout, boolean initSubmodules,
+        Path directory) {
+      this.remote = remote;
+      this.checkout = checkout;
+      this.initSubmodules = initSubmodules;
+      this.directory = directory;
+    }
+
+    @Override
+    public String toString() {
+      return remote + " -> " + directory + " (" + checkout + ") submodules: "
+          + initSubmodules;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (obj == this) {
+        return true;
+      }
+      if (obj == null || !(obj instanceof GitRepositoryDescriptor)) {
+        return false;
+      }
+      GitRepositoryDescriptor other = (GitRepositoryDescriptor) obj;
+      return Objects.equals(remote, other.remote)
+          && Objects.equals(checkout, other.checkout)
+          && Objects.equals(initSubmodules, other.initSubmodules)
+          && Objects.equals(directory, other.directory);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(remote, checkout, initSubmodules, directory);
+    }
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/GitProgressMonitor.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/GitProgressMonitor.java
new file mode 100644
index 0000000..fce6ef1
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/GitProgressMonitor.java
@@ -0,0 +1,72 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.bazel.repository;
+
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.Reporter;
+
+import org.eclipse.jgit.lib.ProgressMonitor;
+
+/**
+ * ProgressMonitor for reporting progress for Git repository rules.
+ */
+class GitProgressMonitor implements ProgressMonitor {
+  private String message;
+  private Reporter reporter;
+  private int totalTasks;
+  private int currentTask;
+
+  private String workTitle;
+  private int totalWork;
+  private int completedWork;
+
+  GitProgressMonitor(String message, Reporter reporter) {
+    this.message = message;
+    this.reporter = reporter;
+  }
+
+  public void start(int totalTasks) {
+    this.totalTasks = totalTasks;
+    this.currentTask = 0;
+  }
+
+  private void report() {
+    reporter.handle(
+        Event.progress("[" + currentTask + " / " + totalTasks + "] "
+            + message + ": " + workTitle + " ("
+            + completedWork + " / " + totalWork + ")"));
+  }
+
+  public void beginTask(String title, int totalWork) {
+    ++currentTask;
+    // TODO(dzc): Remove this when jgit reports totalTasks correctly in start().
+    if (currentTask > totalTasks) {
+      totalTasks = currentTask;
+    }
+    this.totalWork = totalWork;
+    this.completedWork = 0;
+    this.workTitle = title;
+    report();
+  }
+
+  public boolean isCancelled() { return false; }
+
+  public void update(int completed) {
+    completedWork += completed;
+    report();
+  }
+
+  public void endTask() { }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/GitRepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/GitRepositoryFunction.java
new file mode 100644
index 0000000..789e76c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/GitRepositoryFunction.java
@@ -0,0 +1,84 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.bazel.repository;
+
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.bazel.rules.workspace.GitRepositoryRule;
+import com.google.devtools.build.lib.packages.PackageIdentifier.RepositoryName;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.skyframe.FileValue;
+import com.google.devtools.build.lib.skyframe.RepositoryValue;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.skyframe.SkyFunctionException;
+import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
+import com.google.devtools.build.skyframe.SkyFunctionName;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+
+import java.io.IOException;
+
+/**
+ * Clones a Git repository.
+ */
+public class GitRepositoryFunction extends RepositoryFunction {
+  @Override
+  public SkyValue compute(SkyKey skyKey, Environment env) throws SkyFunctionException {
+    RepositoryName repositoryName = (RepositoryName) skyKey.argument();
+    Rule rule = RepositoryFunction.getRule(repositoryName, GitRepositoryRule.NAME, env);
+    if (rule == null) {
+      return null;
+    }
+
+    Path outputDirectory = getExternalRepositoryDirectory().getRelative(rule.getName());
+    FileValue directoryValue = createDirectory(outputDirectory, env, rule);
+    if (directoryValue == null) {
+      return null;
+    }
+
+    try {
+      HttpDownloadValue value = (HttpDownloadValue) env.getValueOrThrow(
+          GitCloneFunction.key(rule, outputDirectory), IOException.class);
+      if (value == null) {
+        return null;
+      }
+    } catch (IOException e) {
+      throw new RepositoryFunctionException(e, Transience.TRANSIENT);
+    }
+
+    return RepositoryValue.create(directoryValue);
+  }
+
+  protected FileValue createDirectory(Path path, Environment env, Rule rule)
+      throws RepositoryFunctionException {
+    try {
+      FileSystemUtils.createDirectoryAndParents(path);
+    } catch (IOException e) {
+      throw new RepositoryFunctionException(new IOException("Could not create directory for "
+          + rule.getName() + ": " + e.getMessage()), Transience.TRANSIENT);
+    }
+    return getRepositoryDirectory(path, env);
+  }
+
+  @Override
+  public SkyFunctionName getSkyFunctionName() {
+    return SkyFunctionName.create(GitRepositoryRule.NAME.toUpperCase());
+  }
+
+  @Override
+  public Class<? extends RuleDefinition> getRuleDefinition() {
+    return GitRepositoryRule.class;
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/NewGitRepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/NewGitRepositoryFunction.java
new file mode 100644
index 0000000..5858332
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/NewGitRepositoryFunction.java
@@ -0,0 +1,66 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.bazel.repository;
+
+import com.google.devtools.build.lib.bazel.rules.workspace.NewGitRepositoryRule;
+import com.google.devtools.build.lib.packages.PackageIdentifier.RepositoryName;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.skyframe.FileValue;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.skyframe.SkyFunctionException;
+import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
+import com.google.devtools.build.skyframe.SkyFunctionName;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+
+import java.io.IOException;
+
+/**
+ * Clones a Git repository, creates a WORKSPACE file, and adds a BUILD file for it.
+ */
+public class NewGitRepositoryFunction extends GitRepositoryFunction {
+  @Override
+  public SkyFunctionName getSkyFunctionName() {
+    return SkyFunctionName.create(NewGitRepositoryRule.NAME.toUpperCase());
+  }
+
+  @Override
+  public SkyValue compute(SkyKey skyKey, Environment env) throws SkyFunctionException {
+    RepositoryName repositoryName = (RepositoryName) skyKey.argument();
+    Rule rule = RepositoryFunction.getRule(repositoryName, NewGitRepositoryRule.NAME, env);
+    if (rule == null) {
+      return null;
+    }
+
+    Path outputDirectory = getExternalRepositoryDirectory().getRelative(rule.getName());
+    FileValue directoryValue = createDirectory(outputDirectory, env, rule);
+    if (directoryValue == null) {
+      return null;
+    }
+
+    try {
+      HttpDownloadValue value = (HttpDownloadValue) env.getValueOrThrow(
+          GitCloneFunction.key(rule, outputDirectory), IOException.class);
+      if (value == null) {
+        return null;
+      }
+    } catch (IOException e) {
+      throw new RepositoryFunctionException(e, Transience.TRANSIENT);
+    }
+
+    createWorkspaceFile(outputDirectory, rule);
+    return symlinkBuildFile(rule, getWorkspace(), directoryValue, env);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
index 8095976..d7ec9ff 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
@@ -60,10 +60,12 @@
 import com.google.devtools.build.lib.bazel.rules.sh.BazelShLibraryRule;
 import com.google.devtools.build.lib.bazel.rules.sh.BazelShRuleClasses;
 import com.google.devtools.build.lib.bazel.rules.sh.BazelShTestRule;
+import com.google.devtools.build.lib.bazel.rules.workspace.GitRepositoryRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.HttpArchiveRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.HttpJarRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.LocalRepositoryRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.MavenJarRule;
+import com.google.devtools.build.lib.bazel.rules.workspace.NewGitRepositoryRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.NewHttpArchiveRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.NewLocalRepositoryRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.WorkspaceBaseRule;
@@ -338,11 +340,13 @@
     builder.addRuleDefinition(new BazelActionListenerRule());
 
     builder.addRuleDefinition(new BindRule());
+    builder.addRuleDefinition(new GitRepositoryRule());
     builder.addRuleDefinition(new HttpArchiveRule());
     builder.addRuleDefinition(new HttpJarRule());
     builder.addRuleDefinition(new LocalRepositoryRule());
     builder.addRuleDefinition(new MavenJarRule());
     builder.addRuleDefinition(new NewHttpArchiveRule());
+    builder.addRuleDefinition(new NewGitRepositoryRule());
     builder.addRuleDefinition(new NewLocalRepositoryRule());
     builder.addRuleDefinition(new AndroidSdkRepositoryRule());
     builder.addRuleDefinition(new AndroidNdkRepositoryRule());
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/GitRepositoryRule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/GitRepositoryRule.java
new file mode 100644
index 0000000..5b5756e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/GitRepositoryRule.java
@@ -0,0 +1,126 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.bazel.rules.workspace;
+
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
+import static com.google.devtools.build.lib.packages.Type.STRING;
+
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.packages.RuleClass;
+
+/**
+ * Rule definition for the git_repository rule.
+ */
+public class GitRepositoryRule implements RuleDefinition {
+  public static final String NAME = "git_repository";
+
+  @Override
+  public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) {
+    return builder
+        /* <!-- #BLAZE_RULE(git_repository).ATTRIBUTE(remote) -->
+        The URI of the remote Git repository.
+        ${SYNOPSIS}
+
+        <p>This must be a HTTP URL. There is currently no support for authentication.</p>
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(attr("remote", STRING).mandatory())
+        /* <!-- #BLAZE_RULE(git_repository).ATTRIBUTE(commit) -->
+        The commit hash to check out in the repository.
+        ${SYNOPSIS}
+
+        <p>Note that one of either <code>commit</code> or <code>tag</code> must be defined.</p>
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(attr("commit", STRING))
+        /* <!-- #BLAZE_RULE(git_repository).ATTRIBUTE(tag) -->
+        The Git tag to check out in the repository.
+        ${SYNOPSIS}
+
+        <p>Note that one of either <code>commit</code> or <code>tag</code> must be defined.</p>
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(attr("tag", STRING))
+        /* <!-- #BLAZE_RULE(git_repository).ATTRIBUTE(init_submodules) -->
+        Whether to clone submodules in the repository.
+        ${SYNOPSIS}
+
+        <p>Currently, only cloning the top-level submodules is supported</p>
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(attr("init_submodules", BOOLEAN).value(false))
+        .setWorkspaceOnly()
+        .build();
+  }
+
+  @Override
+  public Metadata getMetadata() {
+    return RuleDefinition.Metadata.builder()
+        .name(GitRepositoryRule.NAME)
+        .type(RuleClass.Builder.RuleClassType.WORKSPACE)
+        .ancestors(WorkspaceBaseRule.class)
+        .factoryClass(WorkspaceConfiguredTargetFactory.class)
+        .build();
+  }
+}
+
+/*<!-- #BLAZE_RULE (NAME = git_repository, TYPE = OTHER, FAMILY = Workspace)[GENERIC_RULE] -->
+
+${ATTRIBUTE_SIGNATURE}
+
+<p>Clones a Git repository, checks out the specified tag, or commit, and makes its targets
+available for binding.</p>
+
+${ATTRIBUTE_DEFINITION}
+
+<h4 id="git_repository_examples">Examples</h4>
+
+<p>Suppose the current repository contains the source code for a chat program, rooted at the
+  directory <i>~/chat-app</i>. It needs to depend on an SSL library which is available in the
+  remote Git repository <i>http://example.com/openssl/openssl.git</i>. The chat app depends
+  on version 1.0.2 of the SSL library, which is tagged by the v1.0.2 Git tag.<p>
+
+<p>This Git repository contains the following directory structure:</p>
+
+<pre class="code">
+WORKSPACE
+src/
+  BUILD
+  openssl.cc
+  openssl.h
+</pre>
+
+<p><i>src/BUILD</i> contains the following target definition:</p>
+
+<pre class="code">
+cc_library(
+    name = "openssl-lib",
+    srcs = ["openssl.cc"],
+    hdrs = ["openssl.h"],
+)
+</pre>
+
+<p>Targets in the <i>~/chat-app</i> repository can depend on this target if the following lines are
+  added to <i>~/chat-app/WORKSPACE</i>:</p>
+
+<pre class="code">
+git_repository(
+    name = "my-ssl",
+    remote = "http://example.com/openssl/openssl.git",
+    tag = "v1.0.2",
+)
+</pre>
+
+<p>Then targets would specify <code>@my-ssl//src:openssl-lib</code> as a dependency.</p>
+
+<!-- #END_BLAZE_RULE -->*/
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/NewGitRepositoryRule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/NewGitRepositoryRule.java
new file mode 100644
index 0000000..2a57a9b
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/workspace/NewGitRepositoryRule.java
@@ -0,0 +1,135 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.bazel.rules.workspace;
+
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
+import static com.google.devtools.build.lib.packages.Type.STRING;
+
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.packages.RuleClass;
+
+/**
+ * Rule definition for the new_git_repository rule.
+ */
+public class NewGitRepositoryRule implements RuleDefinition {
+  public static final String NAME = "new_git_repository";
+
+  @Override
+  public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) {
+    return builder
+        /* <!-- #BLAZE_RULE(new_git_repository).ATTRIBUTE(remote) -->
+        The URI of the remote Git repository.
+        ${SYNOPSIS}
+
+        <p>This must be a HTTP URL. There is currently no support for authentication.</p>
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(attr("remote", STRING).mandatory())
+        /* <!-- #BLAZE_RULE(git_repository).ATTRIBUTE(commit) -->
+        The commit hash to check out in the repository.
+        ${SYNOPSIS}
+
+        <p>Note that one of either <code>commit</code> or <code>tag</code> must be defined.</p>
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(attr("commit", STRING))
+        /* <!-- #BLAZE_RULE(git_repository).ATTRIBUTE(tag) -->
+        The Git tag to check out in the repository.
+        ${SYNOPSIS}
+
+        <p>Note that one of either <code>commit</code> or <code>tag</code> must be defined.</p>
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(attr("tag", STRING))
+        /* <!-- #BLAZE_RULE(new_git_repository).ATTRIBUTE(build_file) -->
+        A file to use as a BUILD file for this directory.
+        ${SYNOPSIS}
+
+        <p>This path is relative to the build's workspace. The file does not need to be named
+        BUILD, but can be (something like BUILD.new-repo-name may work well for distinguishing it
+        from the repository's actual BUILD files.</p>
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(attr("build_file", STRING).mandatory())
+        /* <!-- #BLAZE_RULE(new_git_repository).ATTRIBUTE(init_submodules) -->
+        Whether to clone submodules in the repository.
+        ${SYNOPSIS}
+
+        <p>Currently, only cloning the top-level submodules is supported</p>
+        <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+        .add(attr("init_submodules", BOOLEAN).value(false))
+        .setWorkspaceOnly()
+        .build();
+  }
+
+  @Override
+  public Metadata getMetadata() {
+    return RuleDefinition.Metadata.builder()
+        .name(NewGitRepositoryRule.NAME)
+        .type(RuleClass.Builder.RuleClassType.WORKSPACE)
+        .ancestors(WorkspaceBaseRule.class)
+        .factoryClass(WorkspaceConfiguredTargetFactory.class)
+        .build();
+  }
+}
+
+/*<!-- #BLAZE_RULE (NAME = new_git_repository, TYPE = OTHER, FAMILY = Workspace)[GENERIC_RULE] -->
+
+${ATTRIBUTE_SIGNATURE}
+
+<p>Clones a Git repository, checks out the specified tag, or commit, and makes its targets
+available for binding.</p>
+
+${ATTRIBUTE_DEFINITION}
+
+<h4 id="git_repository_examples">Examples</h4>
+
+<p>Suppose the current repository contains the source code for a chat program, rooted at the
+  directory <i>~/chat-app</i>. It needs to depend on an SSL library which is available in the
+  remote Git repository <i>http://example.com/openssl/openssl.git</i>. The chat app depends
+  on version 1.0.2 of the SSL library, which is tagged by the v1.0.2 Git tag.<p>
+
+<p>This Git repository contains the following directory structure:</p>
+
+<pre class="code">
+src/
+  openssl.cc
+  openssl.h
+</pre>
+
+<p>In the local repository, the user creates a <i>ssl.BUILD</i> file which contains the following
+target definition:</p>
+
+<pre class="code">
+cc_library(
+    name = "openssl-lib",
+    srcs = ["openssl.cc"],
+    hdrs = ["openssl.h"],
+)
+</pre>
+
+<p>Targets in the <i>~/chat-app</i> repository can depend on this target if the following lines are
+  added to <i>~/chat-app/WORKSPACE</i>:</p>
+
+<pre class="code">
+new_git_repository(
+    name = "my-ssl",
+    remote = "http://example.com/openssl/openssl.git",
+    tag = "v1.0.2",
+    build_file = "ssl.BUILD",
+)
+</pre>
+
+<p>Then targets would specify <code>@my-ssl//src:openssl-lib</code> as a dependency.</p>
+
+<!-- #END_BLAZE_RULE -->*/
diff --git a/src/test/shell/bazel/BUILD b/src/test/shell/bazel/BUILD
index 3880ce0..58a37d8 100644
--- a/src/test/shell/bazel/BUILD
+++ b/src/test/shell/bazel/BUILD
@@ -95,6 +95,16 @@
 )
 
 sh_test(
+    name = "git_repository_test",
+    size = "medium",
+    srcs = ["git_repository_test.sh"],
+    data = [
+        ":test-deps",
+        "//src/test/shell/bazel/testdata:git-repos",
+    ],
+)
+
+sh_test(
     name = "local_repository_test",
     size = "medium",
     srcs = ["local_repository_test.sh"],
diff --git a/src/test/shell/bazel/git_repository_test.sh b/src/test/shell/bazel/git_repository_test.sh
new file mode 100755
index 0000000..580808c
--- /dev/null
+++ b/src/test/shell/bazel/git_repository_test.sh
@@ -0,0 +1,313 @@
+#!/bin/bash
+#
+# Copyright 2015 Google Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Test git_repository and new_git_repository workspace rules.
+#
+
+# Load test environment
+source $(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/test-setup.sh \
+  || { echo "test-setup.sh not found!" >&2; exit 1; }
+
+# Global test setup.
+#
+# Unpacks the test Git repositories in the test temporary directory.
+function set_up() {
+  bazel clean --expunge
+  local repos_dir=$TEST_TMPDIR/repos
+  if [ -e "$repos_dir" ]; then
+    rm -rf $repos_dir
+  fi
+
+  mkdir -p $repos_dir
+  cp $testdata_path/pluto-repo.tar.gz $repos_dir
+  cp $testdata_path/outer-planets-repo.tar.gz $repos_dir
+  cd $repos_dir
+  tar zxvf pluto-repo.tar.gz
+  tar zxvf outer-planets-repo.tar.gz
+}
+
+# Test cloning a Git repository using the git_repository rule.
+#
+# This test uses the pluto Git repository at tag 1-build, which contains the
+# following files:
+#
+# pluto/
+#   WORKSPACE
+#   BUILD
+#   info
+#
+# Then, set up workspace with the following files:
+#
+# $WORKSPACE_DIR/
+#   WORKSPACE
+#   planets/
+#     BUILD
+#     planet_info.sh
+#
+# //planets has a dependency on a target in the pluto Git repository.
+function test_git_repository() {
+  local pluto_repo_dir=$TEST_TMPDIR/repos/pluto
+  # Commit 85b8224 corresponds to tag 1-build. See testdata/pluto.git_log.
+  local commit_hash="b87de93"
+
+  # Create a workspace that clones the repository at the first commit.
+  cd $WORKSPACE_DIR
+  cat > WORKSPACE <<EOF
+git_repository(
+    name = "pluto",
+    remote = "$pluto_repo_dir",
+    commit = "$commit_hash",
+)
+EOF
+  mkdir -p planets
+  cat > planets/BUILD <<EOF
+sh_binary(
+    name = "planet-info",
+    srcs = ["planet_info.sh"],
+    data = ["@pluto//:pluto"],
+)
+EOF
+
+  cat > planets/planet_info.sh <<EOF
+#!/bin/bash
+cat external/pluto/info
+EOF
+  chmod +x planets/planet_info.sh
+
+  bazel run //planets:planet-info >& $TEST_log \
+    || echo "Expected build/run to succeed"
+  expect_log "Pluto is a dwarf planet"
+}
+
+# Test cloning a Git repository using the new_git_repository rule.
+#
+# This test uses the pluto Git repository at tag 0-initial, which contains the
+# following files:
+#
+# pluto/
+#   info
+#
+# Set up workspace with the following files:
+#
+# $WORKSPACE_DIR/
+#   WORKSPACE
+#   pluto.BUILD
+#   planets/
+#     BUILD
+#     planet_info.sh
+#
+# //planets has a dependency on a target in the $TEST_TMPDIR/pluto Git
+# repository.
+function test_new_git_repository() {
+  local pluto_repo_dir=$TEST_TMPDIR/repos/pluto
+
+  # Create a workspace that clones the repository at the first commit.
+  cd $WORKSPACE_DIR
+  cat > WORKSPACE <<EOF
+new_git_repository(
+    name = "pluto",
+    remote = "$pluto_repo_dir",
+    tag = "0-initial",
+    build_file = "pluto.BUILD",
+)
+EOF
+
+  cat > pluto.BUILD <<EOF
+filegroup(
+    name = "pluto",
+    srcs = ["info"],
+    visibility = ["//visibility:public"],
+)
+EOF
+
+  mkdir -p planets
+  cat > planets/BUILD <<EOF
+sh_binary(
+    name = "planet-info",
+    srcs = ["planet_info.sh"],
+    data = ["@pluto//:pluto"],
+)
+EOF
+
+  cat > planets/planet_info.sh <<EOF
+#!/bin/bash
+cat external/pluto/info
+EOF
+  chmod +x planets/planet_info.sh
+
+  bazel run //planets:planet-info >& $TEST_log \
+    || echo "Expected build/run to succeed"
+  expect_log "Pluto is a planet"
+}
+
+# Test cloning a Git repository that has a submodule using the
+# new_git_repository rule.
+#
+# This test uses the outer-planets Git repository at revision 1-submodule, which
+# contains the following files:
+#
+# outer_planets/
+#   neptune/
+#     info
+#   pluto/  --> submodule ../pluto
+#     info
+#
+# Set up workspace with the following files:
+#
+# $WORKSPACE_DIR/
+#   WORKSPACE
+#   outer_planets.BUILD
+#   planets/
+#     BUILD
+#     planet_info.sh
+#
+# planets has a dependency on targets in the $TEST_TMPDIR/outer_planets Git
+# repository.
+function test_new_git_repository_submodules() {
+  local outer_planets_repo_dir=$TEST_TMPDIR/repos/outer-planets
+
+  # Create a workspace that clones the outer_planets repository.
+  cd $WORKSPACE_DIR
+  cat > WORKSPACE <<EOF
+new_git_repository(
+    name = "outer-planets",
+    remote = "$outer_planets_repo_dir",
+    tag = "1-submodule",
+    init_submodules = 1,
+    build_file = "outer_planets.BUILD",
+)
+EOF
+
+  cat > outer_planets.BUILD <<EOF
+filegroup(
+    name = "neptune",
+    srcs = ["neptune/info"],
+    visibility = ["//visibility:public"],
+)
+
+filegroup(
+    name = "pluto",
+    srcs = ["pluto/info"],
+    visibility = ["//visibility:public"],
+)
+EOF
+
+  mkdir -p planets
+  cat > planets/BUILD <<EOF
+sh_binary(
+    name = "planet-info",
+    srcs = ["planet_info.sh"],
+    data = [
+        "@outer-planets//:neptune",
+        "@outer-planets//:pluto",
+    ],
+)
+EOF
+
+  cat > planets/planet_info.sh <<EOF
+#!/bin/bash
+cat external/outer-planets/neptune/info
+cat external/outer-planets/pluto/info
+EOF
+  chmod +x planets/planet_info.sh
+
+  bazel run //planets:planet-info >& $TEST_log \
+    || echo "Expected build/run to succeed"
+  expect_log "Neptune is a planet"
+  expect_log "Pluto is a planet"
+}
+
+# Helper function for setting up the workspace as follows
+#
+# $WORKSPACE_DIR/
+#   WORKSPACE
+#   planets/
+#     planet_info.sh
+#     BUILD
+function setup_error_test() {
+  cd $WORKSPACE_DIR
+  mkdir -p planets
+  cat > planets/planet_info.sh <<EOF
+#!/bin/bash
+cat external/pluto/info
+EOF
+
+  cat > planets/BUILD <<EOF
+sh_binary(
+    name = "planet-info",
+    srcs = ["planet_info.sh"],
+    data = ["@pluto//:pluto"],
+)
+EOF
+}
+
+# Verifies that rule fails if both tag and commit are set.
+#
+# This test uses the pluto Git repository at tag 1-build, which contains the
+# following files:
+#
+# pluto/
+#   WORKSPACE
+#   BUILD
+#   info
+function test_git_repository_both_commit_tag_error() {
+  setup_error_test
+  local pluto_repo_dir=$TEST_TMPDIR/pluto
+  # Commit 85b8224 corresponds to tag 1-build. See testdata/pluto.git_log.
+  local commit_hash="b87de93"
+
+  cd $WORKSPACE_DIR
+  cat > WORKSPACE <<EOF
+git_repository(
+    name = "pluto",
+    remote = "$pluto_repo_dir",
+    tag = "1-build",
+    commit = "$commit_hash",
+)
+EOF
+
+  bazel fetch //planets:planet-info >& $TEST_log \
+    || echo "Expect run to fail."
+  expect_log "One of either commit or tag must be defined"
+}
+
+# Verifies that rule fails if neither tag or commit are set.
+#
+# This test uses the pluto Git repository at tag 1-build, which contains the
+# following files:
+#
+# pluto/
+#   WORKSPACE
+#   BUILD
+#   info
+function test_git_repository_no_commit_tag_error() {
+  setup_error_test
+  local pluto_repo_dir=$TEST_TMPDIR/pluto
+
+  cd $WORKSPACE_DIR
+  cat > WORKSPACE <<EOF
+git_repository(
+    name = "pluto",
+    remote = "$pluto_repo_dir",
+)
+EOF
+
+  bazel fetch //planets:planet-info >& $TEST_log \
+    || echo "Expect run to fail."
+  expect_log "One of either commit or tag must be defined"
+}
+
+run_suite "git_repository tests"
diff --git a/src/test/shell/bazel/testdata/BUILD b/src/test/shell/bazel/testdata/BUILD
new file mode 100644
index 0000000..291d17a
--- /dev/null
+++ b/src/test/shell/bazel/testdata/BUILD
@@ -0,0 +1,9 @@
+filegroup(
+    name = "git-repos",
+    testonly = 1,
+    srcs = [
+        "outer-planets-repo.tar.gz",
+        "pluto-repo.tar.gz",
+    ],
+    visibility = ["//src/test/shell/bazel:__pkg__"],
+)
diff --git a/src/test/shell/bazel/testdata/outer-planets-repo.tar.gz b/src/test/shell/bazel/testdata/outer-planets-repo.tar.gz
new file mode 100644
index 0000000..254a638
--- /dev/null
+++ b/src/test/shell/bazel/testdata/outer-planets-repo.tar.gz
Binary files differ
diff --git a/src/test/shell/bazel/testdata/outer-planets.git_log b/src/test/shell/bazel/testdata/outer-planets.git_log
new file mode 100644
index 0000000..fc26500
--- /dev/null
+++ b/src/test/shell/bazel/testdata/outer-planets.git_log
@@ -0,0 +1,36 @@
+commit c6ffd1361759036836186f06078af8ebc297bf39 (HEAD -> master, tag: 1-submodule)
+Author: John Doe <john@foo.com>
+Date:   Thu Jul 16 04:53:18 2015 -0700
+
+    Add pluto submodule.
+
+diff --git a/.gitmodules b/.gitmodules
+new file mode 100644
+index 0000000..1d2f9b1
+--- /dev/null
++++ b/.gitmodules
+@@ -0,0 +1,3 @@
++[submodule "pluto"]
++	path = pluto
++	url = ../pluto
+diff --git a/pluto b/pluto
+new file mode 160000
+index 0000000..36db6be
+--- /dev/null
++++ b/pluto
+@@ -0,0 +1 @@
++Subproject commit 36db6be50fd9f33cf89fc5b8206f2e3714b520e3
+
+commit eaf1b34982ead69228b6f3f894a0a34c59c07f17 (tag: 0-initial)
+Author: John Doe <john@foo.com>
+Date:   Thu Jul 16 04:52:23 2015 -0700
+
+    Initial commit.
+
+diff --git a/neptune/info b/neptune/info
+new file mode 100644
+index 0000000..41cf7b5
+--- /dev/null
++++ b/neptune/info
+@@ -0,0 +1 @@
++Neptune is a planet
diff --git a/src/test/shell/bazel/testdata/pluto-repo.tar.gz b/src/test/shell/bazel/testdata/pluto-repo.tar.gz
new file mode 100644
index 0000000..a347a37
--- /dev/null
+++ b/src/test/shell/bazel/testdata/pluto-repo.tar.gz
Binary files differ
diff --git a/src/test/shell/bazel/testdata/pluto.git_log b/src/test/shell/bazel/testdata/pluto.git_log
new file mode 100644
index 0000000..fb98d77
--- /dev/null
+++ b/src/test/shell/bazel/testdata/pluto.git_log
@@ -0,0 +1,41 @@
+commit b87de9346bb1a4a3d4d2ab1a567b07c5d11a486a (HEAD -> master, tag: 1-build)
+Author: John Doe <john@foo.com>
+Date:   Thu Jul 16 04:50:53 2015 -0700
+
+    Add WORKSPACE and BUILD file. Update info because Pluto is no longer a planet.
+
+diff --git a/BUILD b/BUILD
+new file mode 100644
+index 0000000..874e03f
+--- /dev/null
++++ b/BUILD
+@@ -0,0 +1,5 @@
++filegroup(
++    name = "pluto",
++    srcs = ["info"],
++    visibility = ["//visibility:public"],
++)
+diff --git a/WORKSPACE b/WORKSPACE
+new file mode 100644
+index 0000000..e69de29
+diff --git a/info b/info
+index 8f442be..36cbae4 100644
+--- a/info
++++ b/info
+@@ -1 +1 @@
+-Pluto is a planet
++Pluto is a dwarf planet
+
+commit 36db6be50fd9f33cf89fc5b8206f2e3714b520e3 (tag: 0-initial)
+Author: John Doe <john@foo.com>
+Date:   Thu Jul 16 04:49:35 2015 -0700
+
+    Initial commit.
+
+diff --git a/info b/info
+new file mode 100644
+index 0000000..8f442be
+--- /dev/null
++++ b/info
+@@ -0,0 +1 @@
++Pluto is a planet
diff --git a/src/test/shell/bazel/testenv.sh b/src/test/shell/bazel/testenv.sh
index 44d15f3..dc0a5c3 100755
--- a/src/test/shell/bazel/testenv.sh
+++ b/src/test/shell/bazel/testenv.sh
@@ -44,6 +44,9 @@
 genclass_path="${TEST_SRCDIR}/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/genclass/GenClass_deploy.jar"
 ijar_path="${TEST_SRCDIR}/third_party/ijar/ijar"
 
+# Test data
+testdata_path=${TEST_SRCDIR}/src/test/shell/bazel/testdata
+
 # Third-party
 PLATFORM="$(uname -s | tr 'A-Z' 'a-z')"
 MACHINE_TYPE="$(uname -m)"
diff --git a/third_party/BUILD b/third_party/BUILD
index 91135d1..1d47272 100644
--- a/third_party/BUILD
+++ b/third_party/BUILD
@@ -217,6 +217,11 @@
 )
 
 java_import(
+    name = "jgit",
+    jars = ["jgit/org.eclipse.jgit-4.0.1.201506240215-r.jar"],
+)
+
+java_import(
     name = "joda_time",
     jars = ["joda_time/joda-time-2.3.jar"],
 )
@@ -274,6 +279,14 @@
 )
 
 java_import(
+    name = "slf4j",
+    jars = [
+        "slf4j/slf4j-api-1.7.7.jar",
+        "slf4j/slf4j-jdk14-1.7.7.jar",
+    ],
+)
+
+java_import(
     name = "tomcat_annotations_api",
     jars = ["tomcat_annotations_api/tomcat-annotations-api-8.0.5.jar"],
 )
diff --git a/third_party/README.md b/third_party/README.md
index 65f3941..e5ce426 100644
--- a/third_party/README.md
+++ b/third_party/README.md
@@ -154,6 +154,13 @@
 * License: Apache License 2.0
 
 
+[jgit](https://eclipse.org/jgit/)
+------
+
+* Version: 4.0.1.201506240215-r
+* License: Eclipse Distribution License 1.0
+
+
 [joda_time](http://www.joda.org/joda-time/)
 -----------
 
@@ -175,6 +182,7 @@
 
 
 [jsr330_inject](https://code.google.com/p/atinject/)
+---------------
 
 * Version: 1
 * License: Apache License 2.0
@@ -213,6 +221,13 @@
 * License: Apache License 2.0
 
 
+[slf4j](http://www.slf4j.org/)
+-------
+
+* Version: 1.7.7
+* License: MIT license
+
+
 Testing
 =======