Create backup branches under refs/backups/

If we create backups as regular branches under refs/heads, then those
will also be handled by the plugin and we can possibly end up with
ridiculous amounts of backups of backups. They will also be replicated
by default, which will cause unnecessary network traffic.

Instead, create the backup branches under the refs/backups/ namespace
using refs/backups/heads for branches and refs/backups/tags for tags.

Split the functionality for generating the backup branch name out into
a utility class, and add unit tests for it.

Update the 'about' documentation with more details.

Change-Id: I434dd27ad371b248f218d60304c0f6c63fb31f6b
diff --git a/BUCK b/BUCK
index 24f8053..3351729 100644
--- a/BUCK
+++ b/BUCK
@@ -9,3 +9,19 @@
     'Gerrit-Module: com.googlesource.gerrit.plugins.refprotection.RefProtectionModule'
   ],
 )
+
+java_test(
+  name = 'ref-protection_tests',
+  srcs = glob(['src/test/java/**/*.java']),
+  deps = [
+    ':ref-protection__plugin',
+    '//gerrit-common:server',
+    '//gerrit-reviewdb:server',
+    '//gerrit-server:server',
+    '//lib:guava',
+    '//lib:gwtorm',
+    '//lib:junit',
+    '//lib:truth',
+    '//lib/jgit:jgit',
+  ],
+)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/refprotection/BackupBranch.java b/src/main/java/com/googlesource/gerrit/plugins/refprotection/BackupBranch.java
new file mode 100644
index 0000000..8c96e9e
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/refprotection/BackupBranch.java
@@ -0,0 +1,22 @@
+package com.googlesource.gerrit.plugins.refprotection;
+
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Constants.R_REFS;
+import static org.eclipse.jgit.lib.Constants.R_TAGS;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class BackupBranch {
+  public static final String R_BACKUPS = R_REFS + "backups/";
+
+  public static String get(String branchName) {
+    if (branchName.startsWith(R_HEADS) || branchName.startsWith(R_TAGS)) {
+      return String.format("%s-%s",
+          R_BACKUPS + branchName.replaceFirst(R_REFS, ""),
+          new SimpleDateFormat("YYYYMMdd-HHmmss").format(new Date()));
+    }
+
+    return branchName;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/refprotection/RefUpdateListener.java b/src/main/java/com/googlesource/gerrit/plugins/refprotection/RefUpdateListener.java
index 1af4547..fcb62d4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/refprotection/RefUpdateListener.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/refprotection/RefUpdateListener.java
@@ -25,8 +25,6 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
 
 class RefUpdateListener implements GitReferenceUpdatedListener {
 
@@ -70,24 +68,20 @@
    * @param event the Event
    */
   private void createBackupBranch(Event event, ProjectResource project) {
-    String branchName = event.getRefName().replaceFirst(R_HEADS, "");
+    String branchName = event.getRefName();
+    String backupRef = BackupBranch.get(branchName);
+
+    // No-op if the backup branch name is same as the original
+    if (backupRef.equals(branchName)) {
+      return;
+    }
+
+    CreateBranch.Input input = new CreateBranch.Input();
+    input.ref = backupRef;
+    input.revision = event.getOldObjectId();
+
     try {
-      String branchPrefix = "";
-      int n = branchName.lastIndexOf("/");
-      if (n != -1) {
-        branchPrefix = branchName.substring(0, n + 1);
-        branchName = branchName.substring(n + 1);
-      }
-
-      String ref =
-          String.format("%sbackup-%s-%s", branchPrefix, branchName,
-              new SimpleDateFormat("YYYYMMdd-HHmmss").format(new Date()));
-
-      CreateBranch.Input input = new CreateBranch.Input();
-      input.ref = ref;
-      input.revision = event.getOldObjectId();
-
-      createBranchFactory.create(ref).apply(project, input);
+      createBranchFactory.create(backupRef).apply(project, input);
     } catch (BadRequestException | AuthException | ResourceConflictException
         | IOException e) {
       log.error(e.getMessage(), e);
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index b917002..c681000 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -1,2 +1,10 @@
-Makes backups of deleted refs.
+Ref protection plugin.
 
+Protects against commits being lost by creating backups of deleted refs under
+the `refs/backups/` namespace.
+
+Branches under `refs/heads/` that are deleted or rewritten are backed up
+as `refs/backups/heads/branch-name-YYYYMMDD-HHmmss`.
+
+Tags under `refs/tags/` that are deleted are backed up (as branches) as
+`refs/backups/tags/tag-name-YYYYMMDD-HHmmss`.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/refprotection/BackupBranchNameTest.java b/src/test/java/com/googlesource/gerrit/plugins/refprotection/BackupBranchNameTest.java
new file mode 100644
index 0000000..b2bc9a2
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/refprotection/BackupBranchNameTest.java
@@ -0,0 +1,28 @@
+package com.googlesource.gerrit.plugins.refprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class BackupBranchNameTest {
+
+  @Test
+  public void backupBranchNameForTag() throws Exception {
+    String name = BackupBranch.get("refs/tags/v1.0");
+    String expected_prefix = BackupBranch.R_BACKUPS + "tags/v1.0-";
+    assertThat(name).startsWith(expected_prefix);
+  }
+
+  @Test
+  public void backupBranchNameForBranch() throws Exception {
+    String name = BackupBranch.get("refs/heads/master");
+    String expected_prefix = BackupBranch.R_BACKUPS + "heads/master-";
+    assertThat(name).startsWith(expected_prefix);
+  }
+
+  @Test
+  public void backupBranchNameForUnsupportedNamespace() throws Exception {
+    String ref = "refs/changes/45/12345/1";
+    assertThat(BackupBranch.get(ref)).isEqualTo(ref);
+  }
+}