James Moger
2014-06-16 6b18b0761b726fd9aef1ebcc21b760378f7d4b5c
src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java
New file
@@ -0,0 +1,372 @@
/*
 * Copyright 2014 gitblit.com.
 *
 * 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.gitblit.wicket.pages;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import com.gitblit.Constants;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.GitBlitException;
import com.gitblit.Keys;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.FileUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.AccessPolicyPanel;
import com.gitblit.wicket.panels.BooleanChoiceOption;
import com.gitblit.wicket.panels.BooleanOption;
import com.gitblit.wicket.panels.RepositoryNamePanel;
public class NewRepositoryPage extends RootSubPage {
   private final RepositoryModel repositoryModel;
   private IModel<Boolean> addReadmeModel;
   private Model<String> gitignoreModel;
   private IModel<Boolean> addGitflowModel;
   private IModel<Boolean> addGitignoreModel;
   private AccessPolicyPanel accessPolicyPanel;
   private RepositoryNamePanel namePanel;
   public NewRepositoryPage() {
      // create constructor
      super();
      repositoryModel = new RepositoryModel();
      setupPage(getString("gb.newRepository"), "");
      setStatelessHint(false);
      setOutputMarkupId(true);
   }
   @Override
   protected boolean requiresPageMap() {
      return true;
   }
   @Override
   protected Class<? extends BasePage> getRootNavPageClass() {
      return RepositoriesPage.class;
   }
   @Override
   protected void onInitialize() {
      super.onInitialize();
      CompoundPropertyModel<RepositoryModel> rModel = new CompoundPropertyModel<>(repositoryModel);
      Form<RepositoryModel> form = new Form<RepositoryModel>("editForm", rModel) {
         private static final long serialVersionUID = 1L;
         @Override
         protected void onSubmit() {
            try {
               if (!namePanel.updateModel(repositoryModel)) {
                  return;
               }
               accessPolicyPanel.updateModel(repositoryModel);
               repositoryModel.owners = new ArrayList<String>();
               repositoryModel.owners.add(GitBlitWebSession.get().getUsername());
               // setup branch defaults
               boolean useGitFlow = addGitflowModel.getObject();
               repositoryModel.HEAD = Constants.R_MASTER;
               repositoryModel.mergeTo = Constants.MASTER;
               if (useGitFlow) {
                  // tickets normally merge to develop unless they are hotfixes
                  repositoryModel.mergeTo = Constants.DEVELOP;
               }
               repositoryModel.allowForks = app().settings().getBoolean(Keys.web.allowForking, true);
               // optionally generate an initial commit
               boolean addReadme = addReadmeModel.getObject();
               String gitignore = null;
               boolean addGitignore = addGitignoreModel.getObject();
               if (addGitignore) {
                  gitignore = gitignoreModel.getObject();
                  if (StringUtils.isEmpty(gitignore)) {
                     throw new GitBlitException(getString("gb.pleaseSelectGitIgnore"));
                  }
               }
               // init the repository
               app().gitblit().updateRepositoryModel(repositoryModel.name, repositoryModel, true);
               // optionally create an initial commit
               initialCommit(repositoryModel, addReadme, gitignore, useGitFlow);
            } catch (GitBlitException e) {
               error(e.getMessage());
               return;
            }
            setRedirect(true);
            setResponsePage(SummaryPage.class, WicketUtils.newRepositoryParameter(repositoryModel.name));
         }
      };
      // do not let the browser pre-populate these fields
      form.add(new SimpleAttributeModifier("autocomplete", "off"));
      namePanel = new RepositoryNamePanel("namePanel", repositoryModel);
      form.add(namePanel);
      // prepare the default access controls
      AccessRestrictionType defaultRestriction = AccessRestrictionType.fromName(
            app().settings().getString(Keys.git.defaultAccessRestriction, AccessRestrictionType.PUSH.name()));
      if (AccessRestrictionType.NONE == defaultRestriction) {
         defaultRestriction = AccessRestrictionType.PUSH;
      }
      AuthorizationControl defaultControl = AuthorizationControl.fromName(
            app().settings().getString(Keys.git.defaultAuthorizationControl, AuthorizationControl.NAMED.name()));
      if (AuthorizationControl.AUTHENTICATED == defaultControl) {
         defaultRestriction = AccessRestrictionType.PUSH;
      }
      repositoryModel.authorizationControl = defaultControl;
      repositoryModel.accessRestriction = defaultRestriction;
      accessPolicyPanel = new AccessPolicyPanel("accessPolicyPanel", repositoryModel);
      form.add(accessPolicyPanel);
      //
      // initial commit options
      //
      // add README
      addReadmeModel = Model.of(false);
      form.add(new BooleanOption("addReadme",
            getString("gb.initWithReadme"),
            getString("gb.initWithReadmeDescription"),
            addReadmeModel));
      // add .gitignore
      File gitignoreDir = app().runtime().getFileOrFolder(Keys.git.gitignoreFolder, "${baseFolder}/gitignore");
      File [] files = gitignoreDir.listFiles();
      if (files == null) {
         files = new File[0];
      }
      List<String> gitignores = new ArrayList<String>();
      for (File file : files) {
         if (file.isFile() && file.getName().endsWith(".gitignore")) {
            gitignores.add(StringUtils.stripFileExtension(file.getName()));
         }
      }
      Collections.sort(gitignores);
      gitignoreModel = Model.of("");
      addGitignoreModel = Model.of(false);
      form.add(new BooleanChoiceOption<String>("addGitIgnore",
            getString("gb.initWithGitignore"),
            getString("gb.initWithGitignoreDescription"),
            addGitignoreModel,
            gitignoreModel,
            gitignores).setVisible(gitignores.size() > 0));
      // TODO consider gitflow at creation (ticket-55)
      addGitflowModel = Model.of(false);
      form.add(new BooleanOption("addGitFlow",
            "Include a .gitflow file",
            "This will generate a config file which guides Git clients in setting up Gitflow branches.",
            addGitflowModel).setVisible(false));
      form.add(new Button("create"));
      add(form);
   }
   /**
    * Prepare the initial commit for the repository.
    *
    * @param repository
    * @param addReadme
    * @param gitignore
    * @param addGitFlow
    * @return true if an initial commit was created
    */
   protected boolean initialCommit(RepositoryModel repository, boolean addReadme, String gitignore,
         boolean addGitFlow) {
      boolean initialCommit = addReadme || !StringUtils.isEmpty(gitignore) || addGitFlow;
      if (!initialCommit) {
         return false;
      }
      // build an initial commit
      boolean success = false;
      Repository db = app().repositories().getRepository(repositoryModel.name);
      ObjectInserter odi = db.newObjectInserter();
      try {
         UserModel user = GitBlitWebSession.get().getUser();
         PersonIdent author = new PersonIdent(user.getDisplayName(), user.emailAddress);
         DirCache newIndex = DirCache.newInCore();
         DirCacheBuilder indexBuilder = newIndex.builder();
         if (addReadme) {
            // insert a README
            String title = StringUtils.stripDotGit(StringUtils.getLastPathElement(repositoryModel.name));
            String description = repositoryModel.description == null ? "" : repositoryModel.description;
            String readme = String.format("## %s\n\n%s\n\n", title, description);
            byte [] bytes = readme.getBytes(Constants.ENCODING);
            DirCacheEntry entry = new DirCacheEntry("README.md");
            entry.setLength(bytes.length);
            entry.setLastModified(System.currentTimeMillis());
            entry.setFileMode(FileMode.REGULAR_FILE);
            entry.setObjectId(odi.insert(org.eclipse.jgit.lib.Constants.OBJ_BLOB, bytes));
            indexBuilder.add(entry);
         }
         if (!StringUtils.isEmpty(gitignore)) {
            // insert a .gitignore file
            File dir = app().runtime().getFileOrFolder(Keys.git.gitignoreFolder, "${baseFolder}/gitignore");
            File file = new File(dir, gitignore + ".gitignore");
            if (file.exists() && file.length() > 0) {
               byte [] bytes = FileUtils.readContent(file);
               if (!ArrayUtils.isEmpty(bytes)) {
                  DirCacheEntry entry = new DirCacheEntry(".gitignore");
                  entry.setLength(bytes.length);
                  entry.setLastModified(System.currentTimeMillis());
                  entry.setFileMode(FileMode.REGULAR_FILE);
                  entry.setObjectId(odi.insert(org.eclipse.jgit.lib.Constants.OBJ_BLOB, bytes));
                  indexBuilder.add(entry);
               }
            }
         }
         if (addGitFlow) {
            // insert a .gitflow file
            Config config = new Config();
            config.setString("gitflow", null, "masterBranch", Constants.MASTER);
            config.setString("gitflow", null, "developBranch", Constants.DEVELOP);
            config.setString("gitflow", null, "featureBranchPrefix", "feature/");
            config.setString("gitflow", null, "releaseBranchPrefix", "release/");
            config.setString("gitflow", null, "hotfixBranchPrefix", "hotfix/");
            config.setString("gitflow", null, "supportBranchPrefix", "support/");
            config.setString("gitflow", null, "versionTagPrefix", "");
            byte [] bytes = config.toText().getBytes(Constants.ENCODING);
            DirCacheEntry entry = new DirCacheEntry(".gitflow");
            entry.setLength(bytes.length);
            entry.setLastModified(System.currentTimeMillis());
            entry.setFileMode(FileMode.REGULAR_FILE);
            entry.setObjectId(odi.insert(org.eclipse.jgit.lib.Constants.OBJ_BLOB, bytes));
            indexBuilder.add(entry);
         }
         indexBuilder.finish();
         if (newIndex.getEntryCount() == 0) {
            // nothing to commit
            return false;
         }
         ObjectId treeId = newIndex.writeTree(odi);
         // Create a commit object
         CommitBuilder commit = new CommitBuilder();
         commit.setAuthor(author);
         commit.setCommitter(author);
         commit.setEncoding(Constants.ENCODING);
         commit.setMessage("Initial commit");
         commit.setTreeId(treeId);
         // Insert the commit into the repository
         ObjectId commitId = odi.insert(commit);
         odi.flush();
         // set the branch refs
         RevWalk revWalk = new RevWalk(db);
         try {
            // set the master branch
            RevCommit revCommit = revWalk.parseCommit(commitId);
            RefUpdate masterRef = db.updateRef(Constants.R_MASTER);
            masterRef.setNewObjectId(commitId);
            masterRef.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
            Result masterRC = masterRef.update();
            switch (masterRC) {
            case NEW:
               success = true;
               break;
            default:
               success = false;
            }
            if (addGitFlow) {
               // set the develop branch for git-flow
               RefUpdate developRef = db.updateRef(Constants.R_DEVELOP);
               developRef.setNewObjectId(commitId);
               developRef.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
               Result developRC = developRef.update();
               switch (developRC) {
               case NEW:
                  success = true;
                  break;
               default:
                  success = false;
               }
            }
         } finally {
            revWalk.release();
         }
      } catch (UnsupportedEncodingException e) {
         logger().error(null, e);
      } catch (IOException e) {
         logger().error(null, e);
      } finally {
         odi.release();
         db.close();
      }
      return success;
   }
}