James Moger
2015-11-22 ed552ba47c02779c270ffd62841d6d1048dade70
commit | author | age
0047fb 1 /*
JM 2  * Copyright 2014 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.gitblit.wicket.pages;
17
18 import java.io.File;
19 import java.io.IOException;
20 import java.io.UnsupportedEncodingException;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.List;
24
25 import org.apache.wicket.behavior.SimpleAttributeModifier;
26 import org.apache.wicket.markup.html.form.Button;
27 import org.apache.wicket.markup.html.form.Form;
28 import org.apache.wicket.model.CompoundPropertyModel;
29 import org.apache.wicket.model.IModel;
30 import org.apache.wicket.model.Model;
31 import org.eclipse.jgit.dircache.DirCache;
32 import org.eclipse.jgit.dircache.DirCacheBuilder;
33 import org.eclipse.jgit.dircache.DirCacheEntry;
34 import org.eclipse.jgit.lib.CommitBuilder;
35 import org.eclipse.jgit.lib.Config;
36 import org.eclipse.jgit.lib.FileMode;
37 import org.eclipse.jgit.lib.ObjectId;
38 import org.eclipse.jgit.lib.ObjectInserter;
39 import org.eclipse.jgit.lib.PersonIdent;
40 import org.eclipse.jgit.lib.RefUpdate;
41 import org.eclipse.jgit.lib.RefUpdate.Result;
42 import org.eclipse.jgit.lib.Repository;
43 import org.eclipse.jgit.revwalk.RevCommit;
44 import org.eclipse.jgit.revwalk.RevWalk;
45
46 import com.gitblit.Constants;
47 import com.gitblit.Constants.AccessRestrictionType;
48 import com.gitblit.Constants.AuthorizationControl;
49 import com.gitblit.GitBlitException;
50 import com.gitblit.Keys;
51 import com.gitblit.models.RepositoryModel;
52 import com.gitblit.models.UserModel;
53 import com.gitblit.utils.ArrayUtils;
54 import com.gitblit.utils.FileUtils;
55 import com.gitblit.utils.StringUtils;
56 import com.gitblit.wicket.GitBlitWebSession;
57 import com.gitblit.wicket.WicketUtils;
577998 58 import com.gitblit.wicket.panels.AccessPolicyPanel;
004bc1 59 import com.gitblit.wicket.panels.BooleanChoiceOption;
f9e51c 60 import com.gitblit.wicket.panels.BooleanOption;
6a437e 61 import com.gitblit.wicket.panels.RepositoryNamePanel;
2da1f2 62 import com.google.common.base.Optional;
0047fb 63
JM 64 public class NewRepositoryPage extends RootSubPage {
65
66     private final RepositoryModel repositoryModel;
67     private IModel<Boolean> addReadmeModel;
68     private Model<String> gitignoreModel;
69     private IModel<Boolean> addGitflowModel;
70     private IModel<Boolean> addGitignoreModel;
6a437e 71     private AccessPolicyPanel accessPolicyPanel;
a3456e 72     private RepositoryNamePanel namePanel;
0047fb 73
JM 74     public NewRepositoryPage() {
75         // create constructor
76         super();
77         repositoryModel = new RepositoryModel();
78
79         setupPage(getString("gb.newRepository"), "");
80
81         setStatelessHint(false);
82         setOutputMarkupId(true);
83     }
84
85     @Override
86     protected boolean requiresPageMap() {
87         return true;
88     }
89
90     @Override
91     protected Class<? extends BasePage> getRootNavPageClass() {
92         return RepositoriesPage.class;
93     }
94
95     @Override
96     protected void onInitialize() {
97         super.onInitialize();
98
99         CompoundPropertyModel<RepositoryModel> rModel = new CompoundPropertyModel<>(repositoryModel);
100         Form<RepositoryModel> form = new Form<RepositoryModel>("editForm", rModel) {
101
102             private static final long serialVersionUID = 1L;
103
104             @Override
105             protected void onSubmit() {
106                 try {
a3456e 107                     if (!namePanel.updateModel(repositoryModel)) {
0047fb 108                         return;
JM 109                     }
6a437e 110                     accessPolicyPanel.updateModel(repositoryModel);
0047fb 111
JM 112                     repositoryModel.owners = new ArrayList<String>();
113                     repositoryModel.owners.add(GitBlitWebSession.get().getUsername());
114
115                     // setup branch defaults
116                     boolean useGitFlow = addGitflowModel.getObject();
117
118                     repositoryModel.HEAD = Constants.R_MASTER;
119                     repositoryModel.mergeTo = Constants.MASTER;
120                     if (useGitFlow) {
121                         // tickets normally merge to develop unless they are hotfixes
122                         repositoryModel.mergeTo = Constants.DEVELOP;
123                     }
124
125                     repositoryModel.allowForks = app().settings().getBoolean(Keys.web.allowForking, true);
126
127                     // optionally generate an initial commit
128                     boolean addReadme = addReadmeModel.getObject();
129                     String gitignore = null;
130                     boolean addGitignore = addGitignoreModel.getObject();
131                     if (addGitignore) {
132                         gitignore = gitignoreModel.getObject();
133                         if (StringUtils.isEmpty(gitignore)) {
496532 134                             throw new GitBlitException(getString("gb.pleaseSelectGitIgnore"));
0047fb 135                         }
JM 136                     }
137
138                     // init the repository
139                     app().gitblit().updateRepositoryModel(repositoryModel.name, repositoryModel, true);
140
141                     // optionally create an initial commit
142                     initialCommit(repositoryModel, addReadme, gitignore, useGitFlow);
143
144                 } catch (GitBlitException e) {
145                     error(e.getMessage());
146                     return;
147                 }
148                 setRedirect(true);
a3456e 149                 setResponsePage(SummaryPage.class, WicketUtils.newRepositoryParameter(repositoryModel.name));
0047fb 150             }
JM 151         };
152
153         // do not let the browser pre-populate these fields
154         form.add(new SimpleAttributeModifier("autocomplete", "off"));
155
a3456e 156         namePanel = new RepositoryNamePanel("namePanel", repositoryModel);
JM 157         form.add(namePanel);
158
2c5088 159         // prepare the default access controls
0047fb 160         AccessRestrictionType defaultRestriction = AccessRestrictionType.fromName(
JM 161                 app().settings().getString(Keys.git.defaultAccessRestriction, AccessRestrictionType.PUSH.name()));
162         if (AccessRestrictionType.NONE == defaultRestriction) {
163             defaultRestriction = AccessRestrictionType.PUSH;
164         }
8845b9 165         AuthorizationControl defaultControl = AuthorizationControl.fromName(
JM 166                 app().settings().getString(Keys.git.defaultAuthorizationControl, AuthorizationControl.NAMED.name()));
167
168         if (AuthorizationControl.AUTHENTICATED == defaultControl) {
169             defaultRestriction = AccessRestrictionType.PUSH;
170         }
0047fb 171
2c5088 172         repositoryModel.authorizationControl = defaultControl;
JM 173         repositoryModel.accessRestriction = defaultRestriction;
0047fb 174
6a437e 175         accessPolicyPanel = new AccessPolicyPanel("accessPolicyPanel", repositoryModel);
JM 176         form.add(accessPolicyPanel);
0047fb 177
JM 178         //
179         // initial commit options
180         //
181
182         // add README
183         addReadmeModel = Model.of(false);
004bc1 184         form.add(new BooleanOption("addReadme",
ba516f 185                 getString("gb.initWithReadme"),
JM 186                 getString("gb.initWithReadmeDescription"),
187                 addReadmeModel));
0047fb 188
JM 189         // add .gitignore
190         File gitignoreDir = app().runtime().getFileOrFolder(Keys.git.gitignoreFolder, "${baseFolder}/gitignore");
191         File [] files = gitignoreDir.listFiles();
192         if (files == null) {
193             files = new File[0];
194         }
195         List<String> gitignores = new ArrayList<String>();
196         for (File file : files) {
197             if (file.isFile() && file.getName().endsWith(".gitignore")) {
198                 gitignores.add(StringUtils.stripFileExtension(file.getName()));
199             }
200         }
201         Collections.sort(gitignores);
ba516f 202
0047fb 203         gitignoreModel = Model.of("");
JM 204         addGitignoreModel = Model.of(false);
004bc1 205         form.add(new BooleanChoiceOption<String>("addGitIgnore",
ba516f 206                 getString("gb.initWithGitignore"),
JM 207                 getString("gb.initWithGitignoreDescription"),
208                 addGitignoreModel,
209                 gitignoreModel,
60676f 210                 gitignores).setVisible(gitignores.size() > 0));
0047fb 211
ba516f 212         // TODO consider gitflow at creation (ticket-55)
0047fb 213         addGitflowModel = Model.of(false);
004bc1 214         form.add(new BooleanOption("addGitFlow",
ba516f 215                 "Include a .gitflow file",
JM 216                 "This will generate a config file which guides Git clients in setting up Gitflow branches.",
217                 addGitflowModel).setVisible(false));
0047fb 218
JM 219         form.add(new Button("create"));
220
221         add(form);
222     }
223
224     /**
225      * Prepare the initial commit for the repository.
226      *
227      * @param repository
228      * @param addReadme
229      * @param gitignore
230      * @param addGitFlow
231      * @return true if an initial commit was created
232      */
233     protected boolean initialCommit(RepositoryModel repository, boolean addReadme, String gitignore,
234             boolean addGitFlow) {
235         boolean initialCommit = addReadme || !StringUtils.isEmpty(gitignore) || addGitFlow;
236         if (!initialCommit) {
237             return false;
238         }
239
240         // build an initial commit
241         boolean success = false;
242         Repository db = app().repositories().getRepository(repositoryModel.name);
243         ObjectInserter odi = db.newObjectInserter();
244         try {
245
246             UserModel user = GitBlitWebSession.get().getUser();
2da1f2 247             String email = Optional.fromNullable(user.emailAddress).or(user.username + "@" + "gitblit");
JM 248             PersonIdent author = new PersonIdent(user.getDisplayName(), email);
0047fb 249
JM 250             DirCache newIndex = DirCache.newInCore();
251             DirCacheBuilder indexBuilder = newIndex.builder();
252
253             if (addReadme) {
254                 // insert a README
255                 String title = StringUtils.stripDotGit(StringUtils.getLastPathElement(repositoryModel.name));
256                 String description = repositoryModel.description == null ? "" : repositoryModel.description;
257                 String readme = String.format("## %s\n\n%s\n\n", title, description);
258                 byte [] bytes = readme.getBytes(Constants.ENCODING);
259
260                 DirCacheEntry entry = new DirCacheEntry("README.md");
261                 entry.setLength(bytes.length);
262                 entry.setLastModified(System.currentTimeMillis());
263                 entry.setFileMode(FileMode.REGULAR_FILE);
264                 entry.setObjectId(odi.insert(org.eclipse.jgit.lib.Constants.OBJ_BLOB, bytes));
265
266                 indexBuilder.add(entry);
267             }
268
269             if (!StringUtils.isEmpty(gitignore)) {
270                 // insert a .gitignore file
271                 File dir = app().runtime().getFileOrFolder(Keys.git.gitignoreFolder, "${baseFolder}/gitignore");
272                 File file = new File(dir, gitignore + ".gitignore");
273                 if (file.exists() && file.length() > 0) {
274                     byte [] bytes = FileUtils.readContent(file);
275                     if (!ArrayUtils.isEmpty(bytes)) {
276                         DirCacheEntry entry = new DirCacheEntry(".gitignore");
277                         entry.setLength(bytes.length);
278                         entry.setLastModified(System.currentTimeMillis());
279                         entry.setFileMode(FileMode.REGULAR_FILE);
280                         entry.setObjectId(odi.insert(org.eclipse.jgit.lib.Constants.OBJ_BLOB, bytes));
281
282                         indexBuilder.add(entry);
283                     }
284                 }
285             }
286
287             if (addGitFlow) {
288                 // insert a .gitflow file
289                 Config config = new Config();
290                 config.setString("gitflow", null, "masterBranch", Constants.MASTER);
291                 config.setString("gitflow", null, "developBranch", Constants.DEVELOP);
292                 config.setString("gitflow", null, "featureBranchPrefix", "feature/");
293                 config.setString("gitflow", null, "releaseBranchPrefix", "release/");
294                 config.setString("gitflow", null, "hotfixBranchPrefix", "hotfix/");
295                 config.setString("gitflow", null, "supportBranchPrefix", "support/");
296                 config.setString("gitflow", null, "versionTagPrefix", "");
297
298                 byte [] bytes = config.toText().getBytes(Constants.ENCODING);
299
300                 DirCacheEntry entry = new DirCacheEntry(".gitflow");
301                 entry.setLength(bytes.length);
302                 entry.setLastModified(System.currentTimeMillis());
303                 entry.setFileMode(FileMode.REGULAR_FILE);
304                 entry.setObjectId(odi.insert(org.eclipse.jgit.lib.Constants.OBJ_BLOB, bytes));
305
306                 indexBuilder.add(entry);
307             }
308
309             indexBuilder.finish();
310
311             if (newIndex.getEntryCount() == 0) {
312                 // nothing to commit
313                 return false;
314             }
315
316             ObjectId treeId = newIndex.writeTree(odi);
317
318             // Create a commit object
319             CommitBuilder commit = new CommitBuilder();
320             commit.setAuthor(author);
321             commit.setCommitter(author);
322             commit.setEncoding(Constants.ENCODING);
323             commit.setMessage("Initial commit");
324             commit.setTreeId(treeId);
325
326             // Insert the commit into the repository
327             ObjectId commitId = odi.insert(commit);
328             odi.flush();
329
330             // set the branch refs
331             RevWalk revWalk = new RevWalk(db);
332             try {
333                 // set the master branch
334                 RevCommit revCommit = revWalk.parseCommit(commitId);
335                 RefUpdate masterRef = db.updateRef(Constants.R_MASTER);
336                 masterRef.setNewObjectId(commitId);
337                 masterRef.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
338                 Result masterRC = masterRef.update();
339                 switch (masterRC) {
340                 case NEW:
341                     success = true;
342                     break;
343                 default:
344                     success = false;
345                 }
346
347                 if (addGitFlow) {
348                     // set the develop branch for git-flow
349                     RefUpdate developRef = db.updateRef(Constants.R_DEVELOP);
350                     developRef.setNewObjectId(commitId);
351                     developRef.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
352                     Result developRC = developRef.update();
353                     switch (developRC) {
354                     case NEW:
355                         success = true;
356                         break;
357                     default:
358                         success = false;
359                     }
360                 }
361             } finally {
a1cee6 362                 revWalk.close();
0047fb 363             }
JM 364         } catch (UnsupportedEncodingException e) {
365             logger().error(null, e);
366         } catch (IOException e) {
367             logger().error(null, e);
368         } finally {
a1cee6 369             odi.close();
0047fb 370             db.close();
JM 371         }
372         return success;
373     }
374 }