Merge "Expose the AnonymousCowardName, GerritPersonIdent and NotesBranchUtil to plugins"
diff --git a/contrib/fake_ldap.pl b/contrib/fake_ldap.pl
new file mode 100644
index 0000000..5d423a78
--- /dev/null
+++ b/contrib/fake_ldap.pl
@@ -0,0 +1,333 @@
+#!/usr/bin/env perl
+
+# Fake LDAP server for Gerrit
+# Author: Olivier Croquette <ocroquette@free.fr>
+# Last change: 2012-11-12
+#
+# Abstract:
+# ====================================================================
+#
+# Gerrit currently supports several authentication schemes, but
+# unfortunately not the most basic one, e.g. local accounts with
+# local passwords.
+#
+# As a workaround, this script implements a minimal LDAP server
+# that can be used to authenticate against Gerrit. The information
+# required by Gerrit relative to users (user ID, password, display
+# name, email) is stored in a text file similar to /etc/passwd
+#
+#
+# Usage (see below for the setup)
+# ====================================================================
+#
+# To create a new file to store the user information:
+#   fake-ldap edituser --datafile /path/datafile --username maxpower \
+#     --displayname "Max Power" --email max.power@provider.com
+#
+# To modify an existing user (for instance the email):
+#   fake-ldap edituser --datafile /path/datafile --username ocroquette \
+#     --email max.power@provider2.com
+#
+# To set a new password for an existing user:
+#   fake-ldap edituser --datafile /path/datafile --username ocroquette \
+#     --password ""
+#
+# To start the server:
+#   fake-ldap start --datafile /path/datafile
+#
+# The server reads the user data file on each new connection. It's not
+# scalable but it should not be a problem for the intended usage
+# (small teams, testing,...)
+#
+#
+# Setup
+# ===================================================================
+#
+# Install the dependencies
+#
+#   Install the Perl module dependencies. On Debian and MacPorts,
+#   all modules are available as packages, except Net::LDAP::Server.
+#
+#   Debian: apt-get install libterm-readkey-perl
+#
+#   Since Net::LDAP::Server consists only of one file, you can put it
+#   along the script in Net/LDAP/Server.pm
+#
+# Create the data file with the first user (see above)
+#
+# Start as the script a server ("start" command, see above)
+#
+# Configure Gerrit with the following options:
+#
+#   gerrit.canonicalWebUrl = ... (workaround for a known Gerrit bug)
+#   auth.type = LDAP_BIND
+#   ldap.server = ldap://localhost:10389
+#   ldap.accountBase = ou=People,dc=nodomain
+#   ldap.groupBase = ou=Group,dc=nodomain
+#
+# Start Gerrit
+#
+# Log on in the Web interface
+#
+# If you want the fake LDAP server to start at boot time, add it to
+# /etc/inittab, with a line like:
+#
+# ld1:6:respawn:su someuser /path/fake-ldap start --datafile /path/datafile
+#
+# ===================================================================
+
+use strict;
+
+# Global var containing the options passed on the command line:
+my %cmdLineOptions;
+
+# Global var containing the user data read from the data file:
+my %userData;
+
+my $defaultport = 10389;
+
+package MyServer;
+
+use Data::Dumper;
+use Net::LDAP::Server;
+use Net::LDAP::Constant qw(LDAP_SUCCESS LDAP_INVALID_CREDENTIALS LDAP_OPERATIONS_ERROR);
+use IO::Socket;
+use IO::Select;
+use Term::ReadKey;
+
+use Getopt::Long;
+
+use base 'Net::LDAP::Server';
+
+sub bind {
+  my $self = shift;
+  my ($reqData, $fullRequest) = @_;
+
+  print "bind called\n" if $cmdLineOptions{verbose} >= 1;
+  print Dumper(\@_) if $cmdLineOptions{verbose} >= 2;
+  my $sha1 = undef;
+  my $uid = undef;
+  eval{
+    $uid = $reqData->{name};
+    $sha1 = main::encryptpwd($uid, $reqData->{authentication}->{simple})
+  };
+  if ($@) {
+    warn $@;
+    return({
+        'matchedDN' => '',
+        'errorMessage' => $@,
+        'resultCode' => LDAP_OPERATIONS_ERROR
+    });
+  }
+
+  print $sha1 . "\n" if $cmdLineOptions{verbose} >= 2;
+  print Dumper($userData{$uid}) . "\n" if $cmdLineOptions{verbose} >= 2;
+
+  if ( defined($sha1) && $sha1 && $userData{$uid} && ( $sha1 eq $userData{$uid}->{password} ) ) {
+    print "authentication of $uid succeeded\n" if $cmdLineOptions{verbose} >= 1;
+    return({
+      'matchedDN' => "dn=$uid,ou=People,dc=nodomain",
+      'errorMessage' => '',
+      'resultCode' => LDAP_SUCCESS
+    });
+  }
+  else {
+    print "authentication of $uid failed\n" if $cmdLineOptions{verbose} >= 1;
+    return({
+      'matchedDN' => '',
+      'errorMessage' => '',
+      'resultCode' => LDAP_INVALID_CREDENTIALS
+    });
+  }
+}
+
+sub search {
+    my $self = shift;
+    my ($reqData, $fullRequest) = @_;
+    print "search called\n" if $cmdLineOptions{verbose} >= 1;
+    print Dumper($reqData)  if $cmdLineOptions{verbose} >= 2;
+    my @entries;
+    if ( $reqData->{baseObject} eq 'ou=People,dc=nodomain' ) {
+        my $uid = $reqData->{filter}->{equalityMatch}->{assertionValue};
+        push @entries, Net::LDAP::Entry->new ( "dn=$uid,ou=People,dc=nodomain",
+       , 'objectName'=>"dn=uid,ou=People,dc=nodomain", 'uid'=>$uid, 'mail'=>$userData{$uid}->{email}, 'displayName'=>$userData{$uid}->{displayName});
+   }
+   elsif ( $reqData->{baseObject} eq 'ou=Group,dc=nodomain'  ) {
+        push @entries, Net::LDAP::Entry->new ( 'dn=Users,ou=Group,dc=nodomain',
+       , 'objectName'=>'dn=Users,ou=Group,dc=nodomain');
+   }
+
+    return {
+        'matchedDN' => '',
+        'errorMessage' => '',
+        'resultCode' => LDAP_SUCCESS
+    }, @entries;
+}
+
+
+package main;
+
+use Digest::SHA1  qw(sha1 sha1_hex sha1_base64);
+
+sub exitWithError {
+  my $msg = shift;
+  print STDERR $msg . "\n";
+  exit(1);
+}
+
+sub encryptpwd {
+  my ($uid, $passwd) = @_;
+  # Use the user id to compute the hash, to avoid rainbox table attacks
+  return sha1_hex($uid.$passwd);
+}
+
+my $result = Getopt::Long::GetOptions (
+  "port=i"        => \$cmdLineOptions{port},
+  "datafile=s"    => \$cmdLineOptions{datafile},
+  "email=s"       => \$cmdLineOptions{email},
+  "displayname=s" => \$cmdLineOptions{displayName},
+  "username=s"    => \$cmdLineOptions{userName},
+  "password=s"    => \$cmdLineOptions{password},
+  "verbose=i"     => \$cmdLineOptions{verbose},
+);
+exitWithError("Failed to parse command line arguments") if ! $result;
+exitWithError("Please provide a valid path for the datafile") if ! $cmdLineOptions{datafile};
+
+my @commands = qw(start edituser);
+if ( @ARGV != 1 || ! grep {$_ eq $ARGV[0]} @commands ) {
+  exitWithError("Please provide a valid command among: " . join(",", @commands));
+}
+
+my $command = $ARGV[0];
+if ( $command eq "start") {
+  startServer();
+}
+elsif ( $command eq "edituser") {
+  editUser();
+}
+
+
+sub startServer() {
+
+  my $port = $cmdLineOptions{port} || $defaultport;
+
+  print "starting on port $port\n" if $cmdLineOptions{verbose} >= 1;
+
+  my $sock = IO::Socket::INET->new(
+    Listen => 5,
+    Proto => 'tcp',
+    Reuse => 1,
+    LocalAddr => "localhost", # Comment this line if Gerrit doesn't run on this host
+    LocalPort => $port
+  );
+
+  my $sel = IO::Select->new($sock);
+  my %Handlers;
+  while (my @ready = $sel->can_read) {
+    foreach my $fh (@ready) {
+      if ($fh == $sock) {
+        # Make sure the data is up to date on new every connection
+        readUserData();
+
+        # let's create a new socket
+        my $psock = $sock->accept;
+        $sel->add($psock);
+        $Handlers{*$psock} = MyServer->new($psock);
+      } else {
+        my $result = $Handlers{*$fh}->handle;
+        if ($result) {
+          # we have finished with the socket
+          $sel->remove($fh);
+          $fh->close;
+          delete $Handlers{*$fh};
+        }
+      }
+    }
+  }
+}
+
+sub readUserData {
+  %userData = ();
+  open (MYFILE, "<$cmdLineOptions{datafile}") || exitWithError("Could not open \"$cmdLineOptions{datafile}\" for reading");
+  while (<MYFILE>) {
+    chomp;
+    my @fields = split(/:/, $_);
+    $userData{$fields[0]} = { password=>$fields[1], displayName=>$fields[2], email=>$fields[3] };
+  }
+  close (MYFILE);
+}
+
+sub writeUserData {
+  open (MYFILE, ">$cmdLineOptions{datafile}") || exitWithError("Could not open \"$cmdLineOptions{datafile}\" for writing");
+  foreach my $userid (sort(keys(%userData))) {
+    my $userInfo = $userData{$userid};
+    print MYFILE join(":",
+      $userid,
+      $userInfo->{password},
+      $userInfo->{displayName},
+      $userInfo->{email}
+      ). "\n";
+  }
+  close (MYFILE);
+}
+
+sub readPassword {
+  Term::ReadKey::ReadMode('noecho');
+  my $password = Term::ReadKey::ReadLine(0);
+  Term::ReadKey::ReadMode('normal');
+  print "\n";
+  return $password;
+}
+
+sub readAndConfirmPassword {
+  print "Please enter the password: ";
+  my $pwd = readPassword();
+  print "Please re-enter the password: ";
+  my $pwdCheck = readPassword();
+  exitWithError("The passwords are different") if $pwd ne $pwdCheck;
+  return $pwd;
+}
+
+sub editUser {
+  exitWithError("Please provide a valid user name") if ! $cmdLineOptions{userName};
+  my $userName = $cmdLineOptions{userName};
+
+  readUserData() if -r $cmdLineOptions{datafile};
+
+  my $encryptedPassword = undef;
+  if ( ! defined($userData{$userName}) ) {
+    # New user
+
+    exitWithError("Please provide a valid display name") if ! $cmdLineOptions{displayName};
+    exitWithError("Please provide a valid email") if ! $cmdLineOptions{email};
+
+    $userData{$userName} = { };
+
+    if ( ! defined($cmdLineOptions{password}) ) {
+      # No password provided on the command line. Force reading from terminal.
+      $cmdLineOptions{password} = "";
+    }
+  }
+
+  if ( defined($cmdLineOptions{password}) && ! $cmdLineOptions{password} ) {
+    $cmdLineOptions{password} = readAndConfirmPassword();
+    exitWithError("Please provide a non empty password") if ! $cmdLineOptions{password};
+  }
+
+
+  if ( $cmdLineOptions{password} ) {
+    $encryptedPassword = encryptpwd($userName, $cmdLineOptions{password});
+  }
+
+
+  $userData{$userName}->{password}    = $encryptedPassword if $encryptedPassword;
+  $userData{$userName}->{displayName} = $cmdLineOptions{displayName} if $cmdLineOptions{displayName};
+  $userData{$userName}->{email}       = $cmdLineOptions{email} if $cmdLineOptions{email};
+  # print Data::Dumper::Dumper(\%userData);
+
+  print "New user data for $cmdLineOptions{userName}:\n";
+  foreach ( sort(keys(%{$userData{$userName}}))) {
+    printf "  %-15s : %s\n", $_, $userData{$userName}->{$_}
+  }
+  writeUserData();
+}
\ No newline at end of file
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
index 81d4fc9..67e9de6 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
@@ -28,7 +28,7 @@
    * has this capability can perform almost any other action, or can grant
    * themselves the power to perform any other action on the site. Most of
    * the other capabilities and permissions fall-back to the predicate
-   * "OR user has capablity ADMINISTRATE_SERVER".
+   * "OR user has capability ADMINISTRATE_SERVER".
    */
   public static final String ADMINISTRATE_SERVER = "administrateServer";
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/CurrentUserPopupPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/CurrentUserPopupPanel.java
index e559d38..e30c366 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/CurrentUserPopupPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/CurrentUserPopupPanel.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.client;
 
+import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.uibinder.client.UiBinder;
@@ -35,11 +36,14 @@
   Label userEmail;
   @UiField
   Anchor logout;
+  @UiField
+  Anchor settings;
 
   public CurrentUserPopupPanel(Account account, boolean canLogOut) {
     super(/* auto hide */true, /* modal */false);
     setWidget(binder.createAndBindUi(this));
     setStyleName(Gerrit.RESOURCES.css().userInfoPopup());
+    settings.setHref(Gerrit.selfRedirect(PageLinks.SETTINGS));
     if (account.getFullName() != null) {
       userName.setText(account.getFullName());
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/CurrentUserPopupPanel.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/CurrentUserPopupPanel.ui.xml
index a50f11c..4dbfc23 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/CurrentUserPopupPanel.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/CurrentUserPopupPanel.ui.xml
@@ -18,7 +18,6 @@
 <ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
   xmlns:g='urn:import:com.google.gwt.user.client.ui'>
   <ui:with field='constants' type='com.google.gerrit.client.GerritConstants'/>
-  <ui:import field='com.google.gerrit.common.PageLinks.SETTINGS'/>
 
   <ui:style>
     .panel {
@@ -39,7 +38,7 @@
   <g:FlowPanel styleName="{style.panel}">
     <g:Label ui:field='userName' styleName="{style.userName}" />
     <g:Label ui:field='userEmail' styleName="{style.email}" />
-    <g:Anchor href='{SETTINGS}'>
+    <g:Anchor ui:field='settings'>
       <ui:text from='{constants.menuSettings}' />
     </g:Anchor>
     <g:Anchor ui:field='logout' styleName="{style.logout}">
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index d7db9ac..e72e127 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -434,8 +434,8 @@
         }
         c = dashboardId.indexOf(":");
         if (0 <= c) {
-          final String ref = URL.decode(dashboardId.substring(0, c));
-          final String path = URL.decode(dashboardId.substring(c + 1));
+          final String ref = URL.decodeQueryString(dashboardId.substring(0, c));
+          final String path = URL.decodeQueryString(dashboardId.substring(c + 1));
           DashboardList.get(new Project.NameKey(project), ref + ":" + path, cb);
           return;
         }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardList.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardList.java
index f4da5fa..2fb4c17 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardList.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/dashboards/DashboardList.java
@@ -50,11 +50,11 @@
   private static String encodeDashboardId(String dashboardId) {
     int c = dashboardId.indexOf(":");
     if (0 <= c) {
-      final String ref = URL.encode(dashboardId.substring(0, c));
-      final String path = URL.encode(dashboardId.substring(c + 1));
+      final String ref = URL.encodeQueryString(dashboardId.substring(0, c));
+      final String path = URL.encodeQueryString(dashboardId.substring(c + 1));
       return ref + ":" + path;
     } else {
-      return URL.encode(dashboardId);
+      return URL.encodeQueryString(dashboardId);
     }
   }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index 546ac71..dad9b80 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -289,6 +289,7 @@
 
       ReceiveCommits rc = (ReceiveCommits) request.getAttribute(ATT_RC);
       ReceivePack rp = rc.getReceivePack();
+      rp.getAdvertiseRefsHook().advertiseRefs(rp);
       ProjectControl pc = (ProjectControl) request.getAttribute(ATT_CONTROL);
       Project.NameKey projectName = pc.getProject().getNameKey();
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
index 2b00cc7..5667996 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -39,6 +39,7 @@
 import com.google.inject.servlet.ServletModule;
 
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
 
 import java.io.IOException;
 
@@ -173,8 +174,9 @@
         while (name.endsWith("/")) {
           name = name.substring(0, name.length() - 1);
         }
-        if (name.endsWith(".git")) {
-          name = name.substring(0, name.length() - 4);
+        if (name.endsWith(Constants.DOT_GIT_EXT)) {
+          name = name.substring(0, //
+              name.length() - Constants.DOT_GIT_EXT.length());
         }
         while (name.endsWith("/")) {
           name = name.substring(0, name.length() - 1);
diff --git a/gerrit-package-plugins/pom.xml b/gerrit-package-plugins/pom.xml
index 5a4cdbb..9f11bb3 100644
--- a/gerrit-package-plugins/pom.xml
+++ b/gerrit-package-plugins/pom.xml
@@ -43,7 +43,7 @@
     <dependency>
       <groupId>com.googlesource.gerrit.plugins.replication</groupId>
       <artifactId>replication</artifactId>
-      <version>1.0</version>
+      <version>1.0-rc0</version>
       <scope>provided</scope>
     </dependency>
   </dependencies>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectControlHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectControlHandler.java
index da033e7..836f246 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectControlHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectControlHandler.java
@@ -20,6 +20,7 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
+import org.eclipse.jgit.lib.Constants;
 import org.kohsuke.args4j.CmdLineException;
 import org.kohsuke.args4j.CmdLineParser;
 import org.kohsuke.args4j.OptionDef;
@@ -42,23 +43,12 @@
   @Override
   public final int parseArguments(final Parameters params)
       throws CmdLineException {
-    final String token = params.getParameter(0);
-    String projectName = token;
+    String projectName = params.getParameter(0);
 
     while (projectName.endsWith("/")) {
       projectName = projectName.substring(0, projectName.length() - 1);
     }
 
-    if (projectName.endsWith(".git")) {
-      // Be nice and drop the trailing ".git" suffix, which we never keep
-      // in our database, but clients might mistakenly provide anyway.
-      //
-      projectName = projectName.substring(0, projectName.length() - 4);
-      while (projectName.endsWith("/")) {
-        projectName = projectName.substring(0, projectName.length() - 1);
-      }
-    }
-
     while (projectName.startsWith("/")) {
       // Be nice and drop the leading "/" if supplied by an absolute path.
       // We don't have a file system hierarchy, just a flat namespace in
@@ -68,12 +58,25 @@
       projectName = projectName.substring(1);
     }
 
+    String nameWithoutSuffix = projectName;
+    if (nameWithoutSuffix.endsWith(Constants.DOT_GIT_EXT)) {
+      // Be nice and drop the trailing ".git" suffix, which we never keep
+      // in our database, but clients might mistakenly provide anyway.
+      //
+      nameWithoutSuffix = nameWithoutSuffix.substring(0, //
+          nameWithoutSuffix.length() - Constants.DOT_GIT_EXT.length());
+      while (nameWithoutSuffix.endsWith("/")) {
+        nameWithoutSuffix =
+            nameWithoutSuffix.substring(0, nameWithoutSuffix.length() - 1);
+      }
+    }
+
     final ProjectControl control;
     try {
-      Project.NameKey nameKey = new Project.NameKey(projectName);
+      Project.NameKey nameKey = new Project.NameKey(nameWithoutSuffix);
       control = projectControlFactory.validateFor(nameKey, ProjectControl.OWNER | ProjectControl.VISIBLE);
     } catch (NoSuchProjectException e) {
-      throw new CmdLineException(owner, "'" + token + "': not a Gerrit project");
+      throw new CmdLineException(owner, "'" + projectName + "': is not a Gerrit project");
     }
 
     setter.addValue(control);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 578fcc0..5ec33a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -159,6 +159,7 @@
         + "To push into this reference you need 'Push' rights."),
         DELETE("You need 'Push' rights with the 'Force Push'\n"
             + "flag set to delete references."),
+        DELETE_CHANGES("Cannot delete from 'refs/changes'"),
         CODE_REVIEW("You need 'Push' rights to upload code review requests.\n"
             + "Verify that you are pushing to the right branch."),
         CREATE("You are not allowed to perform this operation.\n"
@@ -947,7 +948,10 @@
 
   private void parseDelete(final ReceiveCommand cmd) {
     RefControl ctl = projectControl.controlForRef(cmd.getRefName());
-    if (ctl.canDelete()) {
+    if (ctl.getRefName().startsWith("refs/changes/")) {
+      errors.put(Error.DELETE_CHANGES, ctl.getRefName());
+      reject(cmd, "cannot delete changes");
+    } else if (ctl.canDelete()) {
       batch.addCommand(cmd);
     } else {
       if (GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index 380b40b..3561c0c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -81,7 +81,7 @@
       final Change c, final String mc) {
     super(ea, anonymousCowardName, mc);
     change = c;
-    changeData = change != null ? new ChangeData(change) : null;
+    changeData = new ChangeData(change);
     emailOnlyAuthors = false;
   }
 
@@ -208,7 +208,7 @@
 
   /** Get a link to the change; null if the server doesn't know its own address. */
   public String getChangeUrl() {
-    if (change != null && getGerritUrl() != null) {
+    if (getGerritUrl() != null) {
       final StringBuilder r = new StringBuilder();
       r.append(getGerritUrl());
       r.append(change.getChangeId());
@@ -356,10 +356,6 @@
   /** Returns all watches that are relevant */
   protected final Watchers getWatches(NotifyType type) throws OrmException {
     Watchers matching = new Watchers();
-    if (changeData == null) {
-      return matching;
-    }
-
     Set<Account.Id> projectWatchers = new HashSet<Account.Id>();
 
     for (AccountProjectWatch w : args.db.get().accountProjectWatches()
@@ -545,7 +541,6 @@
 
   protected boolean isVisibleTo(final Account.Id to) throws OrmException {
     return projectState == null
-        || change == null
         || projectState.controlFor(args.identifiedUserFactory.create(to))
             .controlFor(change).isVisible(args.db.get());
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
index 3ff2c2a..20c2e15 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
@@ -73,7 +73,6 @@
 
   private boolean isOwnerOfProjectOrBranch(Account.Id user) {
     return projectState != null
-        && change != null
         && projectState.controlFor(args.identifiedUserFactory.create(user))
           .controlForRef(change.getDest())
           .isOwner();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
index d27c205..bb231a5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -278,7 +278,11 @@
       try {
         couldMerge = m.merge(b.getParents());
       } catch (IOException e) {
-        //
+        // It is not safe to continue further down in this method as throwing
+        // an exception most likely means that the merge tree was not created
+        // and m.getMergeResults() is empty. This would mean that all paths are
+        // unmerged and Gerrit UI would show all paths in the patch list.
+        return null;
       }
 
       if (couldMerge) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
index 5049e9f..67a6d20 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
@@ -229,7 +229,6 @@
 
   private Injector newRootInjector(final PluginGuiceEnvironment env) {
     List<Module> modules = Lists.newArrayListWithCapacity(4);
-    modules.add(env.getSysModule());
     if (apiType == ApiType.PLUGIN) {
       modules.add(env.getSysModule());
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index 4e6f626..229b062 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -58,6 +58,7 @@
   private Boolean owner;
   private Boolean canForgeAuthor;
   private Boolean canForgeCommitter;
+  private Boolean isVisible;
 
   RefControl(ProjectControl projectControl, String ref,
       PermissionCollection relevant) {
@@ -103,8 +104,12 @@
 
   /** Can this user see this reference exists? */
   public boolean isVisible() {
-    return (getCurrentUser() instanceof InternalUser || canPerform(Permission.READ))
-        && canRead();
+    if (isVisible == null) {
+      isVisible =
+          (getCurrentUser() instanceof InternalUser || canPerform(Permission.READ))
+              && canRead();
+    }
+    return isVisible;
   }
 
   /**
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
index df2e123..fbd8236 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
@@ -103,8 +103,9 @@
             fromIndex = urlExtractedPath.lastIndexOf('/', fromIndex - 1);
             projectName = urlExtractedPath.substring(fromIndex + 1);
 
-            if (projectName.endsWith(".git")) {
-              projectName = projectName.substring(0, projectName.length() - 4);
+            if (projectName.endsWith(Constants.DOT_GIT_EXT)) {
+              projectName = projectName.substring(0, //
+                  projectName.length() - Constants.DOT_GIT_EXT.length());
             }
 
             if (repoManager.list().contains(new Project.NameKey(projectName))) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
index 93d86e5..7294d4c 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
@@ -27,6 +27,7 @@
 
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
 import org.eclipse.jgit.lib.BlobBasedConfig;
+import org.eclipse.jgit.lib.Constants;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -214,8 +215,9 @@
         while (fromIndex > 0) {
           fromIndex = urlExtractedPath.lastIndexOf('/', fromIndex - 1);
           projectNameCandidate = urlExtractedPath.substring(fromIndex + 1);
-          if (projectNameCandidate.endsWith(".git")) {
-            projectNameCandidate = projectNameCandidate.substring(0, projectNameCandidate.length() - 4);
+          if (projectNameCandidate.endsWith(Constants.DOT_GIT_EXT)) {
+            projectNameCandidate = projectNameCandidate.substring(0, //
+                projectNameCandidate.length() - Constants.DOT_GIT_EXT.length());
           }
           if (projectNameCandidate.equals(reposToBeFound.get(id))) {
             expect(repoManager.list()).andReturn(