Remove the branches table from the database

This table is actually unnecessary, and causes some confusion when
setting up a new Gerrit installation, or a new project.

Bug: issue 299
Change-Id: If19f3474a7b5e3adba3b09c470a01927fe46cb58
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/Documentation/project-setup.txt b/Documentation/project-setup.txt
index 29aab86..6210808 100644
--- a/Documentation/project-setup.txt
+++ b/Documentation/project-setup.txt
@@ -47,10 +47,7 @@
 Register Project
 ~~~~~~~~~~~~~~~~
 
-At least two inserts are needed to register a project with Gerrit:
-one to define the project exists, and another to define a branch
-that changes can be uploaded to for code review.  Additional branches
-may be defined if desired.
+One insert is needed to register a project with Gerrit.
 
 [NOTE]
 Note that the `.git` suffix is not typically included in the
@@ -69,13 +66,6 @@
   ,'N'
   ,'M'
   ,'new/project');
-
-  INSERT INTO branches
-  (branch_name
-  ,project_name)
-  VALUES
-  ('refs/heads/master'
-  ,'new/project');
 ====
 
 [NOTE]
diff --git a/src/main/java/com/google/gerrit/client/reviewdb/Branch.java b/src/main/java/com/google/gerrit/client/reviewdb/Branch.java
index 04328f4..06b18e6 100644
--- a/src/main/java/com/google/gerrit/client/reviewdb/Branch.java
+++ b/src/main/java/com/google/gerrit/client/reviewdb/Branch.java
@@ -17,7 +17,7 @@
 import com.google.gwtorm.client.Column;
 import com.google.gwtorm.client.StringKey;
 
-/** Registered line of development within a {@link Project}. */
+/** Line of development within a {@link Project}. */
 public final class Branch {
   public static final String R_HEADS = "refs/heads/";
   public static final String R_REFS = "refs/";
@@ -69,8 +69,8 @@
     }
   }
 
-  @Column(name = Column.NONE)
   protected NameKey name;
+  protected RevId revision;
 
   protected Branch() {
   }
@@ -90,4 +90,12 @@
   public String getShortName() {
     return name.getShortName();
   }
+
+  public RevId getRevision() {
+    return revision;
+  }
+
+  public void setRevision(final RevId id) {
+    revision = id;
+  }
 }
diff --git a/src/main/java/com/google/gerrit/client/reviewdb/BranchAccess.java b/src/main/java/com/google/gerrit/client/reviewdb/BranchAccess.java
deleted file mode 100644
index 3394ce3..0000000
--- a/src/main/java/com/google/gerrit/client/reviewdb/BranchAccess.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2008 The Android Open Source Project
-//
-// 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.gerrit.client.reviewdb;
-
-import com.google.gwtorm.client.Access;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.client.PrimaryKey;
-import com.google.gwtorm.client.Query;
-import com.google.gwtorm.client.ResultSet;
-
-public interface BranchAccess extends Access<Branch, Branch.NameKey> {
-  @PrimaryKey("name")
-  Branch get(Branch.NameKey name) throws OrmException;
-
-  @Query("WHERE name.projectName = ? ORDER BY name.branchName")
-  ResultSet<Branch> byProject(Project.NameKey key) throws OrmException;
-}
diff --git a/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java b/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java
index d06e3a8..3a2fc6d 100644
--- a/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java
+++ b/src/main/java/com/google/gerrit/client/reviewdb/ReviewDb.java
@@ -88,9 +88,6 @@
   ProjectRightAccess projectRights();
 
   @Relation
-  BranchAccess branches();
-
-  @Relation
   ChangeAccess changes();
 
   @Relation
diff --git a/src/main/java/com/google/gerrit/server/rpc/project/AddBranch.java b/src/main/java/com/google/gerrit/server/rpc/project/AddBranch.java
index 08f185c..f9622d6 100644
--- a/src/main/java/com/google/gerrit/server/rpc/project/AddBranch.java
+++ b/src/main/java/com/google/gerrit/server/rpc/project/AddBranch.java
@@ -16,7 +16,6 @@
 
 import com.google.gerrit.client.reviewdb.Branch;
 import com.google.gerrit.client.reviewdb.Project;
-import com.google.gerrit.client.reviewdb.ReviewDb;
 import com.google.gerrit.client.rpc.InvalidNameException;
 import com.google.gerrit.client.rpc.InvalidRevisionException;
 import com.google.gerrit.git.GitRepositoryManager;
@@ -25,12 +24,9 @@
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.rpc.Handler;
-import com.google.gwtorm.client.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.Constants;
@@ -40,9 +36,10 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.ObjectWalk;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.Collections;
 import java.util.List;
 
 class AddBranch extends Handler<List<Branch>> {
@@ -59,7 +56,6 @@
   private final IdentifiedUser identifiedUser;
   private final GitRepositoryManager repoManager;
   private final ReplicationQueue replication;
-  private final ReviewDb db;
 
   private final Project.NameKey projectName;
   private final String branchName;
@@ -69,7 +65,7 @@
   AddBranch(final ProjectControl.Factory projectControlFactory,
       final ListBranches.Factory listBranchesFactory,
       final IdentifiedUser identifiedUser, final GitRepositoryManager repoManager,
-      final ReplicationQueue replication, final ReviewDb db,
+      final ReplicationQueue replication,
 
       @Assisted Project.NameKey projectName,
       @Assisted("branchName") String branchName,
@@ -79,7 +75,6 @@
     this.identifiedUser = identifiedUser;
     this.repoManager = repoManager;
     this.replication = replication;
-    this.db = db;
 
     this.projectName = projectName;
     this.branchName = branchName;
@@ -87,7 +82,7 @@
   }
 
   @Override
-  public List<Branch> call() throws NoSuchProjectException, OrmException,
+  public List<Branch> call() throws NoSuchProjectException,
       InvalidNameException, InvalidRevisionException, IOException {
     final ProjectControl projectControl =
         projectControlFactory.validateFor(projectName, ProjectControl.OWNER
@@ -141,9 +136,6 @@
       repo.close();
     }
 
-    final Branch newBranch = new Branch(name);
-    db.branches().insert(Collections.singleton(newBranch));
-
     return listBranchesFactory.create(projectName).call();
   }
 
diff --git a/src/main/java/com/google/gerrit/server/rpc/project/DeleteBranches.java b/src/main/java/com/google/gerrit/server/rpc/project/DeleteBranches.java
index 0c77034..5f47bb9 100644
--- a/src/main/java/com/google/gerrit/server/rpc/project/DeleteBranches.java
+++ b/src/main/java/com/google/gerrit/server/rpc/project/DeleteBranches.java
@@ -16,24 +16,21 @@
 
 import com.google.gerrit.client.reviewdb.Branch;
 import com.google.gerrit.client.reviewdb.Project;
-import com.google.gerrit.client.reviewdb.ReviewDb;
 import com.google.gerrit.git.GitRepositoryManager;
 import com.google.gerrit.git.ReplicationQueue;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.rpc.Handler;
-import com.google.gwtorm.client.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -49,21 +46,19 @@
   private final ProjectControl.Factory projectControlFactory;
   private final GitRepositoryManager repoManager;
   private final ReplicationQueue replication;
-  private final ReviewDb db;
 
   private final Project.NameKey projectName;
   private final Set<Branch.NameKey> toRemove;
 
   @Inject
   DeleteBranches(final ProjectControl.Factory projectControlFactory,
-      final GitRepositoryManager repoManager, final ReplicationQueue replication,
-      final ReviewDb db,
+      final GitRepositoryManager repoManager,
+      final ReplicationQueue replication,
 
       @Assisted Project.NameKey name, @Assisted Set<Branch.NameKey> toRemove) {
     this.projectControlFactory = projectControlFactory;
     this.repoManager = repoManager;
     this.replication = replication;
-    this.db = db;
 
     this.projectName = name;
     this.toRemove = toRemove;
@@ -71,7 +66,7 @@
 
   @Override
   public Set<Branch.NameKey> call() throws NoSuchProjectException,
-      RepositoryNotFoundException, OrmException {
+      RepositoryNotFoundException {
     final ProjectControl projectControl =
         projectControlFactory.validateFor(projectName, ProjectControl.OWNER
             | ProjectControl.VISIBLE);
@@ -89,14 +84,10 @@
     final Repository r = repoManager.openRepository(projectName.get());
     try {
       for (final Branch.NameKey branchKey : toRemove) {
-        final Branch b = db.branches().get(branchKey);
-        if (b == null) {
-          continue;
-        }
-
+        final String refname = branchKey.get();
         final RefUpdate.Result result;
         try {
-          final RefUpdate u = r.updateRef(b.getName());
+          final RefUpdate u = r.updateRef(refname);
           u.setForceUpdate(true);
           result = u.delete();
         } catch (IOException e) {
@@ -109,9 +100,8 @@
           case NO_CHANGE:
           case FAST_FORWARD:
           case FORCED:
-            db.branches().delete(Collections.singleton(b));
             deleted.add(branchKey);
-            replication.scheduleUpdate(projectName, b.getName());
+            replication.scheduleUpdate(projectName, refname);
             break;
 
           case REJECTED_CURRENT_BRANCH:
diff --git a/src/main/java/com/google/gerrit/server/ssh/commands/AdminCreateProject.java b/src/main/java/com/google/gerrit/server/ssh/commands/AdminCreateProject.java
index 91143f1..1c76b05 100644
--- a/src/main/java/com/google/gerrit/server/ssh/commands/AdminCreateProject.java
+++ b/src/main/java/com/google/gerrit/server/ssh/commands/AdminCreateProject.java
@@ -16,7 +16,6 @@
 
 import com.google.gerrit.client.reviewdb.AccountGroup;
 import com.google.gerrit.client.reviewdb.ApprovalCategory;
-import com.google.gerrit.client.reviewdb.Branch;
 import com.google.gerrit.client.reviewdb.Project;
 import com.google.gerrit.client.reviewdb.ProjectRight;
 import com.google.gerrit.client.reviewdb.ReviewDb;
@@ -30,9 +29,9 @@
 import com.google.gwtorm.client.Transaction;
 import com.google.inject.Inject;
 
-import org.kohsuke.args4j.Option;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Repository;
+import org.kohsuke.args4j.Option;
 
 import java.io.PrintWriter;
 import java.util.Collections;
@@ -130,10 +129,6 @@
     pr.setMaxValue((short) 1);
     pr.setMinValue((short) 1);
     db.projectRights().insert(Collections.singleton(pr), txn);
-
-    final Branch newBranch =
-        new Branch(new Branch.NameKey(newProjectNameKey, branch));
-    db.branches().insert(Collections.singleton(newBranch), txn);
   }
 
   private void validateParameters() throws Failure {
diff --git a/src/main/java/com/google/gerrit/server/ssh/commands/Receive.java b/src/main/java/com/google/gerrit/server/ssh/commands/Receive.java
index 68ab670..e8c38de 100644
--- a/src/main/java/com/google/gerrit/server/ssh/commands/Receive.java
+++ b/src/main/java/com/google/gerrit/server/ssh/commands/Receive.java
@@ -57,9 +57,6 @@
 import com.google.gwtorm.client.Transaction;
 import com.google.inject.Inject;
 
-import org.kohsuke.args4j.Option;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
@@ -81,6 +78,9 @@
 import org.eclipse.jgit.transport.ReceivePack;
 import org.eclipse.jgit.transport.ReceiveCommand.Result;
 import org.eclipse.jgit.transport.ReceiveCommand.Type;
+import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -168,7 +168,7 @@
   private ReceivePack rp;
   private PersonIdent refLogIdent;
   private ReceiveCommand newChange;
-  private Branch destBranch;
+  private Branch.NameKey destBranch;
 
   private final List<Change.Id> allNewChanges = new ArrayList<Change.Id>();
   private final Map<Change.Id, ReplaceRequest> replaceByChange =
@@ -223,11 +223,9 @@
             if (isHead(c)) {
               switch (c.getType()) {
                 case CREATE:
-                  insertBranchEntity(c);
                   autoCloseChanges(c);
                   break;
                 case DELETE:
-                  deleteBranchEntity(c);
                   break;
                 case UPDATE:
                 case UPDATE_NONFASTFORWARD:
@@ -541,15 +539,30 @@
       destBranchName = Constants.R_HEADS + destBranchName;
     }
 
-    try {
-      destBranch =
-          db.branches().get(
-              new Branch.NameKey(project.getNameKey(), destBranchName));
-    } catch (OrmException e) {
-      log.error("Cannot lookup branch " + project + " " + destBranchName, e);
-      reject(cmd, "database error");
-      return;
+    if (rp.getAdvertisedRefs().containsKey(destBranchName)) {
+      // We advertised the branch to the client so we know
+      // the branch exists. Target this branch for the upload.
+      //
+      destBranch = new Branch.NameKey(project.getNameKey(), destBranchName);
+
+    } else {
+      // We didn't advertise the branch, because it doesn't exist yet.
+      // Allow it anyway if HEAD is a symbolic reference to the name.
+      //
+      final String head;
+      try {
+        head = repo.getFullBranch();
+      } catch (IOException e) {
+        log.error("Cannot read HEAD symref", e);
+        reject(cmd, "internal error");
+        return;
+      }
+
+      if (head.equals(destBranchName)) {
+        destBranch = new Branch.NameKey(project.getNameKey(), destBranchName);
+      }
     }
+
     if (destBranch == null) {
       String n = destBranchName;
       if (n.startsWith(Constants.R_HEADS))
@@ -797,8 +810,7 @@
 
     final Transaction txn = db.beginTransaction();
     final Change change =
-        new Change(changeKey, new Change.Id(db.nextChangeId()), me, destBranch
-            .getNameKey());
+        new Change(changeKey, new Change.Id(db.nextChangeId()), me, destBranch);
     final PatchSet ps = new PatchSet(change.newPatchSetId());
     ps.setCreatedOn(change.getCreatedOn());
     ps.setUploader(me);
@@ -1240,46 +1252,6 @@
     return true;
   }
 
-  private void insertBranchEntity(final ReceiveCommand c) {
-    try {
-      final Branch.NameKey nameKey =
-          new Branch.NameKey(project.getNameKey(), c.getRefName());
-      final Branch b = new Branch(nameKey);
-      db.branches().insert(Collections.singleton(b));
-    } catch (OrmException e) {
-      final String msg = "database failure creating " + c.getRefName();
-      log.error(msg, e);
-
-      try {
-        err.write(("remote error: " + msg + "\n").getBytes("UTF-8"));
-        err.flush();
-      } catch (IOException e2) {
-        // Ignore errors writing to the client
-      }
-    }
-  }
-
-  private void deleteBranchEntity(final ReceiveCommand c) {
-    try {
-      final Branch.NameKey nameKey =
-          new Branch.NameKey(project.getNameKey(), c.getRefName());
-      final Branch b = db.branches().get(nameKey);
-      if (b != null) {
-        db.branches().delete(Collections.singleton(b));
-      }
-    } catch (OrmException e) {
-      final String msg = "database failure deleting " + c.getRefName();
-      log.error(msg, e);
-
-      try {
-        err.write(("remote error: " + msg + "\n").getBytes("UTF-8"));
-        err.flush();
-      } catch (IOException e2) {
-        // Ignore errors writing to the client
-      }
-    }
-  }
-
   private void autoCloseChanges(final ReceiveCommand cmd) {
     final RevWalk rw = rp.getRevWalk();
     try {
diff --git a/src/main/webapp/WEB-INF/sql/upgrade018_019_mysql.sql b/src/main/webapp/WEB-INF/sql/upgrade018_019_mysql.sql
index e24b355..a0de31b 100644
--- a/src/main/webapp/WEB-INF/sql/upgrade018_019_mysql.sql
+++ b/src/main/webapp/WEB-INF/sql/upgrade018_019_mysql.sql
@@ -29,4 +29,6 @@
 ALTER TABLE account_groups MODIFY group_type VARCHAR(8) NOT NULL;
 ALTER TABLE account_groups DROP automatic_membership;
 
+DROP TABLE branches;
+
 UPDATE schema_version SET version_nbr = 19;
diff --git a/src/main/webapp/WEB-INF/sql/upgrade018_019_postgres.sql b/src/main/webapp/WEB-INF/sql/upgrade018_019_postgres.sql
index 45de402..fbf7c81 100644
--- a/src/main/webapp/WEB-INF/sql/upgrade018_019_postgres.sql
+++ b/src/main/webapp/WEB-INF/sql/upgrade018_019_postgres.sql
@@ -33,6 +33,8 @@
 ALTER TABLE account_groups ALTER group_type SET NOT NULL;
 ALTER TABLE account_groups DROP automatic_membership;
 
+DROP TABLE branches;
+
 UPDATE schema_version SET version_nbr = 19;
 
 COMMIT;