Merge branch 'stable-2.11' into master

* origin/stable-2.11:
  Fix buck test in standalone BUCK build
  Add standalone BUCK build

Change-Id: Id849e37156268a04f125bff3b59c804427bbaa04
diff --git a/BUCK b/BUCK
index 26aafae..17cda8a 100644
--- a/BUCK
+++ b/BUCK
@@ -18,7 +18,7 @@
   resources = glob(['src/main/resources/**/*']),
   manifest_entries = [
     'Implementation-Title: Ref Protection plugin',
-    'Implementation-URL: http://review-plus.sonyericsson.net/#/admin/projects/gerrit/plugins/ref-protection',
+    'Implementation-URL: http://gerrit.googlesource.com/plugins/ref-protection',
     'Gerrit-PluginName: ref-protection',
     'Gerrit-Module: com.googlesource.gerrit.plugins.refprotection.RefProtectionModule'
   ],
diff --git a/src/main/java/com/googlesource/gerrit/plugins/refprotection/BackupBranch.java b/src/main/java/com/googlesource/gerrit/plugins/refprotection/BackupBranch.java
deleted file mode 100644
index dbed47a..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/refprotection/BackupBranch.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  The MIT License
- *
- *  Copyright 2015 Sony Mobile Communications AB. All rights reserved.
- *
- *  Permission is hereby granted, free of charge, to any person obtaining a copy
- *  of this software and associated documentation files (the "Software"), to deal
- *  in the Software without restriction, including without limitation the rights
- *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- *  copies of the Software, and to permit persons to whom the Software is
- *  furnished to do so, subject to the following conditions:
- *
- *  The above copyright notice and this permission notice shall be included in
- *  all copies or substantial portions of the Software.
- *
- *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- *  THE SOFTWARE.
- */
-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/BackupRef.java b/src/main/java/com/googlesource/gerrit/plugins/refprotection/BackupRef.java
new file mode 100644
index 0000000..af075c9
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/refprotection/BackupRef.java
@@ -0,0 +1,214 @@
+/*
+ *  The MIT License
+ *
+ *  Copyright 2015 Sony Mobile Communications AB. All rights reserved.
+ *
+ *  Permission is hereby granted, free of charge, to any person obtaining a copy
+ *  of this software and associated documentation files (the "Software"), to deal
+ *  in the Software without restriction, including without limitation the rights
+ *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ *  copies of the Software, and to permit persons to whom the Software is
+ *  furnished to do so, subject to the following conditions:
+ *
+ *  The above copyright notice and this permission notice shall be included in
+ *  all copies or substantial portions of the Software.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ *  THE SOFTWARE.
+ */
+package com.googlesource.gerrit.plugins.refprotection;
+
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.events.RefUpdatedEvent;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.CreateBranch;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.TagBuilder;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class BackupRef {
+  public static final String R_BACKUPS = R_REFS + "backups/";
+  private static final Logger log =
+      LoggerFactory.getLogger(BackupRef.class);
+  private final CreateBranch.Factory createBranchFactory;
+  @Inject private static PluginConfigFactory cfg;
+  @Inject private static GitRepositoryManager repoManager;
+  @Inject @PluginName private static String pluginName;
+
+  @Inject
+  BackupRef(CreateBranch.Factory createBranchFactory) {
+    this.createBranchFactory = createBranchFactory;
+  }
+
+  public void createBackup(RefUpdatedEvent event, ProjectResource project) {
+    String refName = event.getRefName();
+
+    try (Repository git = repoManager.openRepository(project.getNameKey())) {
+      String backupRef = get(project, refName);
+
+      // No-op if the backup branch name is same as the original
+      if (backupRef.equals(refName)) {
+        return;
+      }
+
+      try (RevWalk revWalk = new RevWalk(git)) {
+        if (cfg.getFromGerritConfig(pluginName).getBoolean("createTag",
+            false)) {
+          TagBuilder tag = new TagBuilder();
+          tag.setTagger(
+              new PersonIdent(event.submitter.name, event.submitter.email));
+          tag.setObjectId(revWalk
+              .parseCommit(ObjectId.fromString(event.refUpdate.oldRev)));
+          String update = "Non-fast-forward update to";
+          if (event.refUpdate.newRev.equals(ObjectId.zeroId().getName())) {
+            update = "Deleted";
+          }
+          String type = "branch";
+          String fullMessage = "";
+          if (event.refUpdate.refName.startsWith(R_TAGS)) {
+            type = "tag";
+            try {
+              RevTag origTag =
+                  revWalk.parseTag(ObjectId.fromString(event.refUpdate.oldRev));
+              SimpleDateFormat format = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy ZZZZ");
+              PersonIdent taggerIdent = origTag.getTaggerIdent();
+              String tagger =
+                  String.format("Tagger: %s <%s>\nDate:   %s",
+                      taggerIdent.getName(), taggerIdent.getEmailAddress(),
+                      format.format(taggerIdent.getWhen()));
+              fullMessage = "\n\nOriginal tag:\n" + tagger + "\n\n" + origTag.getFullMessage();
+            } catch (MissingObjectException e) {
+              log.warn("Original tag does not exist", e);
+            } catch (IncorrectObjectTypeException e) {
+              log.warn("Original tag was not a tag", e);
+            } catch (IOException e) {
+              log.warn("Unable to read original tag details", e);
+            }
+          }
+          tag.setMessage(update + " " + type + " " + event.refUpdate.refName + fullMessage);
+          tag.setTag(backupRef);
+
+          ObjectInserter inserter = git.newObjectInserter();
+          ObjectId tagId = inserter.insert(tag);
+          inserter.flush();
+          RefUpdate tagRef = git.updateRef(tag.getTag());
+          tagRef.setNewObjectId(tagId);
+          tagRef.setRefLogMessage("tagged deleted branch/tag " + tag.getTag(),
+              false);
+          tagRef.setForceUpdate(false);
+          Result result = tagRef.update();
+          switch (result) {
+            case NEW:
+            case FORCED:
+              log.debug("Successfully created backup tag");
+              break;
+
+            case LOCK_FAILURE:
+              log.error("Failed to lock repository while creating backup tag");
+              break;
+
+            case REJECTED:
+              log.error("Tag already exists while creating backup tag");
+              break;
+
+            default:
+              log.error("Unknown error while creating backup tag");
+          }
+        } else {
+          CreateBranch.Input input = new CreateBranch.Input();
+          input.ref = backupRef;
+          // We need to parse the commit to ensure if it's a tag, we get the
+          // commit the tag points to!
+          input.revision = ObjectId.toString(
+              revWalk.parseCommit(ObjectId.fromString(event.refUpdate.oldRev))
+                  .getId());
+
+          try {
+            createBranchFactory.create(backupRef).apply(project, input);
+          } catch (BadRequestException | AuthException
+              | ResourceConflictException | IOException e) {
+            log.error(e.getMessage(), e);
+          }
+        }
+      }
+    } catch (RepositoryNotFoundException e) {
+      log.error("Repository does not exist", e);
+    } catch (IOException e) {
+      log.error("Could not open repository", e);
+    }
+  }
+
+  static String get(ProjectResource project, String refName) {
+    if (cfg.getFromGerritConfig(pluginName).getBoolean("useTimestamp", true)) {
+      return getTimestampBranch(refName);
+    }
+    else {
+      return getSequentialBranch(project, refName);
+    }
+  }
+
+  private static String getTimestampBranch(String refName) {
+    if (refName.startsWith(R_HEADS) || refName.startsWith(R_TAGS)) {
+      return String.format("%s-%s",
+          R_BACKUPS + refName.replaceFirst(R_REFS, ""),
+          new SimpleDateFormat("YYYYMMdd-HHmmss").format(new Date()));
+    }
+
+    return refName;
+  }
+
+  private static String getSequentialBranch(ProjectResource project, String branchName) {
+    Integer rev = 1;
+    String deletedName = branchName.replaceFirst(R_REFS, "");
+    try (Repository git = repoManager.openRepository(project.getNameKey())) {
+      for (Ref ref : git.getAllRefs().values()) {
+        String name = ref.getName();
+        if (name.startsWith(R_BACKUPS + deletedName + "/")) {
+          Integer thisNum =
+              Integer.parseInt(name.substring(name.lastIndexOf('/') + 1));
+          if (thisNum >= rev) {
+            rev = thisNum + 1;
+          }
+        }
+      }
+    } catch (RepositoryNotFoundException e) {
+      log.error("Repository does not exist", e);
+    } catch (IOException e) {
+      log.error("Could not determine latest revision of deleted branch", e);
+    }
+
+    return R_BACKUPS + deletedName + "/" + rev;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/refprotection/RefProtectionModule.java b/src/main/java/com/googlesource/gerrit/plugins/refprotection/RefProtectionModule.java
index 96d250c..b2700c7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/refprotection/RefProtectionModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/refprotection/RefProtectionModule.java
@@ -23,14 +23,15 @@
  */
 package com.googlesource.gerrit.plugins.refprotection;
 
-import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
+import com.google.gerrit.common.EventListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.inject.AbstractModule;
 
 public class RefProtectionModule extends AbstractModule {
   @Override
   protected void configure() {
-    DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(
+    DynamicSet.bind(binder(), EventListener.class).to(
         RefUpdateListener.class);
+    requestStaticInjection(BackupRef.class);
   }
 }
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 09d9c94..0ca08f4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/refprotection/RefUpdateListener.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/refprotection/RefUpdateListener.java
@@ -26,14 +26,14 @@
 import static org.eclipse.jgit.lib.Constants.R_HEADS;
 import static org.eclipse.jgit.lib.Constants.R_TAGS;
 
-import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.common.EventListener;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.project.CreateBranch;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.project.ProjectResource;
@@ -49,75 +49,62 @@
 
 import java.io.IOException;
 
-class RefUpdateListener implements GitReferenceUpdatedListener {
+class RefUpdateListener implements EventListener {
 
   private static final Logger log =
       LoggerFactory.getLogger(RefUpdateListener.class);
-  private final CreateBranch.Factory createBranchFactory;
   private final ProjectControl.GenericFactory projectControl;
   private final CurrentUser user;
   private final GitRepositoryManager repoManager;
+  private final BackupRef backupRef;
+  private final boolean protectDeleted;
+  private final boolean protectFastForward;
 
   @Inject
-  RefUpdateListener(CreateBranch.Factory createBranchFactory,
-      ProjectControl.GenericFactory p,
+  RefUpdateListener(ProjectControl.GenericFactory p,
       CurrentUser user,
-      GitRepositoryManager repoManager) {
-    this.createBranchFactory = createBranchFactory;
+      GitRepositoryManager repoManager,
+      BackupRef backupRef,
+      PluginConfigFactory cfg,
+      @PluginName String pluginName) {
     this.projectControl = p;
     this.user = user;
     this.repoManager = repoManager;
+    this.backupRef = backupRef;
+    this.protectDeleted =
+        cfg.getFromGerritConfig(pluginName).getBoolean("protectDeleted", true);
+    this.protectFastForward =
+        cfg.getFromGerritConfig(pluginName).getBoolean("protectFastForward", true);
   }
 
   @Override
-  public void onGitReferenceUpdated(final Event event) {
-    if (isRelevantRef(event)) {
-      Project.NameKey nameKey = new Project.NameKey(event.getProjectName());
-      try {
-        ProjectResource project =
-            new ProjectResource(projectControl.controlFor(nameKey, user));
-        if (isRefDeleted(event) || isNonFastForwardUpdate(event, project)) {
-          createBackupBranch(event, project);
+  public void onEvent(Event event) {
+    if (event instanceof RefUpdatedEvent) {
+      RefUpdatedEvent refUpdate = (RefUpdatedEvent)event;
+      if ((protectDeleted || protectFastForward) && isRelevantRef(refUpdate)) {
+        Project.NameKey nameKey = refUpdate.getProjectNameKey();
+        try {
+          ProjectResource project =
+              new ProjectResource(projectControl.controlFor(nameKey, user));
+          if ((protectDeleted && isRefDeleted(refUpdate))
+              || (protectFastForward && isNonFastForwardUpdate(refUpdate,
+                  project))) {
+            backupRef.createBackup(refUpdate, project);
+          }
+        } catch (NoSuchProjectException | IOException e) {
+          log.error(e.getMessage(), e);
         }
-      } catch (NoSuchProjectException | IOException e) {
-        log.error(e.getMessage(), e);
       }
     }
   }
 
   /**
-   * Create a backup branch for the given ref.
-   *
-   * @param event the Event
-   */
-  private void createBackupBranch(Event event, ProjectResource project) {
-    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 {
-      createBranchFactory.create(backupRef).apply(project, input);
-    } catch (BadRequestException | AuthException | ResourceConflictException
-        | IOException e) {
-      log.error(e.getMessage(), e);
-    }
-  }
-
-  /**
    * Is the event on a relevant ref?
    *
    * @param event the Event
    * @return True if relevant, otherwise False.
    */
-  private boolean isRelevantRef(Event event) {
+  private boolean isRelevantRef(RefUpdatedEvent event) {
     return (!isNewRef(event)) &&
            (event.getRefName().startsWith(R_HEADS)
             || event.getRefName().startsWith(R_TAGS));
@@ -129,8 +116,8 @@
    * @param event the Event
    * @return True if a new ref, otherwise False.
    */
-  private boolean isNewRef(Event event) {
-    return event.getOldObjectId().equals(ObjectId.zeroId().getName());
+  private boolean isNewRef(RefUpdatedEvent event) {
+    return event.refUpdate.oldRev.equals(ObjectId.zeroId().getName());
   }
 
   /**
@@ -139,11 +126,11 @@
    * @param event the Event
    * @return True if a ref deletion, otherwise False.
    */
-  private boolean isRefDeleted(Event event) {
-    if (event.getNewObjectId().equals(ObjectId.zeroId().getName())) {
+  private boolean isRefDeleted(RefUpdatedEvent event) {
+    if (event.refUpdate.newRev.equals(ObjectId.zeroId().getName())) {
       log.info(String.format(
           "Ref Deleted: project [%s] refname [%s] old object id [%s]",
-          event.getProjectName(), event.getRefName(), event.getOldObjectId()));
+          event.getProjectNameKey().toString(), event.getRefName(), event.refUpdate.oldRev));
       return true;
     }
     return false;
@@ -155,14 +142,19 @@
    * @param event the Event
    * @return True if a non-fast-forward update, otherwise False.
    */
-  private boolean isNonFastForwardUpdate(Event event, ProjectResource project)
+  private boolean isNonFastForwardUpdate(RefUpdatedEvent event, ProjectResource project)
       throws RepositoryNotFoundException, IOException {
+    if (isRefDeleted(event)) {
+      // Can't be non-fast-forward if the ref was deleted, and
+      // attempting a check would cause a MissingObjectException.
+      return false;
+    }
     try (Repository repo = repoManager.openRepository(project.getNameKey())) {
       try (RevWalk walk = new RevWalk(repo)) {
         RevCommit oldCommit =
-            walk.parseCommit(repo.resolve(event.getOldObjectId()));
+            walk.parseCommit(repo.resolve(event.refUpdate.oldRev));
         RevCommit newCommit =
-            walk.parseCommit(repo.resolve(event.getNewObjectId()));
+            walk.parseCommit(repo.resolve(event.refUpdate.newRev));
         return !walk.isMergedInto(oldCommit, newCommit);
       }
     }
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index c681000..4d2d541 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -1,10 +1,24 @@
 Ref protection plugin.
 
-Protects against commits being lost by creating backups of deleted refs under
-the `refs/backups/` namespace.
+Protects against commits being lost by creating backups of deleted refs 
+(or non-fast-forward commits) under the `refs/backups/` namespace.
+
+Branch deletion protection can be disabled by setting 
+`plugin.ref-protection.protectDeleted false` in `gerrit.config`.
+Similarly, non-fast-forward update protection can be disabled with
+`plugin.ref-protection.protectFastForward false`.
 
 Branches under `refs/heads/` that are deleted or rewritten are backed up
-as `refs/backups/heads/branch-name-YYYYMMDD-HHmmss`.
+as `refs/backups/heads/branch-name-YYYYMMDD-HHmmss` by default, or as
+sequentially increasing numbers under `refs/backups/heads/branch-name/#`
+by setting `plugin.ref-protection.useTimestamp false`.
 
 Tags under `refs/tags/` that are deleted are backed up (as branches) as
-`refs/backups/tags/tag-name-YYYYMMDD-HHmmss`.
+`refs/backups/tags/tag-name-YYYYMMDD-HHmmss` or as sequentially
+increasing numbers under `refs/backups/tags/branch-name/#` using the same
+`plugin.ref-protection.useTimestamp` setting.
+
+By default, the backups are created as branches.  Optionally, they may
+be created as tags, containing information about the original ref that
+was changed, as well as the user that performed the change.  This can
+be enabled by setting `plugin.ref-protection.createTag true`.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/refprotection/BackupBranchNameTest.java b/src/test/java/com/googlesource/gerrit/plugins/refprotection/BackupBranchNameTest.java
deleted file mode 100644
index b2bc9a2..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/refprotection/BackupBranchNameTest.java
+++ /dev/null
@@ -1,28 +0,0 @@
-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);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/refprotection/BackupRefNameTest.java b/src/test/java/com/googlesource/gerrit/plugins/refprotection/BackupRefNameTest.java
new file mode 100644
index 0000000..d38f3b5
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/refprotection/BackupRefNameTest.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 BackupRefNameTest {
+
+  @Test
+  public void backupTimestampRefNameForTag() throws Exception {
+    String name = BackupRef.get(null, "refs/tags/v1.0");
+    String expected_prefix = BackupRef.R_BACKUPS + "tags/v1.0-";
+    assertThat(name).startsWith(expected_prefix);
+  }
+
+  @Test
+  public void backupTimestampRefNameForBranch() throws Exception {
+    String name = BackupRef.get(null, "refs/heads/master");
+    String expected_prefix = BackupRef.R_BACKUPS + "heads/master-";
+    assertThat(name).startsWith(expected_prefix);
+  }
+
+  @Test
+  public void backupTimestampRefNameForUnsupportedNamespace() throws Exception {
+    String ref = "refs/changes/45/12345/1";
+    assertThat(BackupRef.get(null, ref)).isEqualTo(ref);
+  }
+}