[Notice] Announcing the End of Demo Server [Read me]

Merge branch 'feature/online-readme' of dlab/hive
from pull request 1168
@779fe6aef63dda2a5adb6f7ab3a5967c8d948e5c
--- app/assets/stylesheets/less/_page.less
+++ app/assets/stylesheets/less/_page.less
... | ... | @@ -2432,7 +2432,6 @@ |
2432 | 2432 |
.default { |
2433 | 2433 |
padding:20px; |
2434 | 2434 |
text-align:center; |
2435 |
- min-height:540px; |
|
2436 | 2435 |
.v { display:inline-block; width:0; height:500px; vertical-align:middle; } |
2437 | 2436 |
} |
2438 | 2437 |
.readme-wrap { |
--- app/controllers/BoardApp.java
+++ app/controllers/BoardApp.java
... | ... | @@ -21,7 +21,6 @@ |
21 | 21 |
package controllers; |
22 | 22 |
|
23 | 23 |
import actions.AnonymousCheckAction; |
24 |
-import actions.DefaultProjectCheckAction; |
|
25 | 24 |
import actions.NullProjectCheckAction; |
26 | 25 |
|
27 | 26 |
import com.avaje.ebean.ExpressionList; |
... | ... | @@ -41,6 +40,8 @@ |
41 | 40 |
import play.mvc.Call; |
42 | 41 |
import play.mvc.Result; |
43 | 42 |
import play.mvc.With; |
43 |
+import playRepository.BareCommit; |
|
44 |
+import playRepository.BareRepository; |
|
44 | 45 |
import utils.AccessControl; |
45 | 46 |
import utils.ErrorViews; |
46 | 47 |
import utils.JodaDateUtil; |
... | ... | @@ -98,7 +99,20 @@ |
98 | 99 |
boolean isAllowedToNotice = |
99 | 100 |
AccessControl.isProjectResourceCreatable(UserApp.currentUser(), project, ResourceType.BOARD_NOTICE); |
100 | 101 |
|
101 |
- return ok(create.render("post.new", new Form<>(Posting.class), project, isAllowedToNotice)); |
|
102 |
+ String preparedBodyText = ""; |
|
103 |
+ if(readmeEditRequested() && projectHasReadme(project)){ |
|
104 |
+ preparedBodyText = BareRepository.readREADME(project); |
|
105 |
+ } |
|
106 |
+ |
|
107 |
+ return ok(create.render("post.new", new Form<>(Posting.class), project, isAllowedToNotice, preparedBodyText)); |
|
108 |
+ } |
|
109 |
+ |
|
110 |
+ private static boolean projectHasReadme(Project project) { |
|
111 |
+ return project.readme() != null; |
|
112 |
+ } |
|
113 |
+ |
|
114 |
+ private static boolean readmeEditRequested() { |
|
115 |
+ return request().getQueryString("readme") != null; |
|
102 | 116 |
} |
103 | 117 |
|
104 | 118 |
@Transactional |
... | ... | @@ -110,7 +124,7 @@ |
110 | 124 |
if (postForm.hasErrors()) { |
111 | 125 |
boolean isAllowedToNotice = |
112 | 126 |
AccessControl.isProjectResourceCreatable(UserApp.currentUser(), project, ResourceType.BOARD_NOTICE); |
113 |
- return badRequest(create.render("error.validation", postForm, project, isAllowedToNotice)); |
|
127 |
+ return badRequest(create.render("error.validation", postForm, project, isAllowedToNotice, "")); |
|
114 | 128 |
} |
115 | 129 |
|
116 | 130 |
final Posting post = postForm.get(); |
... | ... | @@ -124,19 +138,42 @@ |
124 | 138 |
post.setAuthor(UserApp.currentUser()); |
125 | 139 |
post.project = project; |
126 | 140 |
|
141 |
+ if (post.readme) { |
|
142 |
+ Posting readmePosting = Posting.findREADMEPosting(project); |
|
143 |
+ if (readmePosting != null) { |
|
144 |
+ return editPost(userName, projectName, readmePosting.getNumber()); |
|
145 |
+ } else { |
|
146 |
+ commitReadmeFile(project, post); |
|
147 |
+ } |
|
148 |
+ } |
|
127 | 149 |
post.save(); |
128 |
- |
|
129 | 150 |
attachUploadFilesToPost(post.asResource()); |
130 |
- |
|
131 | 151 |
NotificationEvent.afterNewPost(post); |
132 | 152 |
|
153 |
+ if (post.readme) { |
|
154 |
+ return redirect(routes.ProjectApp.project(userName, projectName)); |
|
155 |
+ } |
|
133 | 156 |
return redirect(routes.BoardApp.post(project.owner, project.name, post.getNumber())); |
157 |
+ } |
|
158 |
+ |
|
159 |
+ private static void commitReadmeFile(Project project, Posting post){ |
|
160 |
+ BareCommit bare = new BareCommit(project, UserApp.currentUser()); |
|
161 |
+ try{ |
|
162 |
+ bare.commitTextFile("README.md", post.body, post.title); |
|
163 |
+ } catch (IOException e) { |
|
164 |
+ e.printStackTrace(); |
|
165 |
+ play.Logger.error(e.getMessage()); |
|
166 |
+ } |
|
134 | 167 |
} |
135 | 168 |
|
136 | 169 |
@IsAllowed(value = Operation.READ, resourceType = ResourceType.BOARD_POST) |
137 | 170 |
public static Result post(String userName, String projectName, Long number) { |
138 | 171 |
Project project = Project.findByOwnerAndProjectName(userName, projectName); |
139 | 172 |
Posting post = Posting.findByNumber(project, number); |
173 |
+ |
|
174 |
+ if(post.readme){ |
|
175 |
+ post.body = BareRepository.readREADME(project); |
|
176 |
+ } |
|
140 | 177 |
|
141 | 178 |
if (request().getHeader("Accept").contains("application/json")) { |
142 | 179 |
ObjectNode json = Json.newObject(); |
... | ... | @@ -163,6 +200,9 @@ |
163 | 200 |
Form<Posting> editForm = new Form<>(Posting.class).fill(posting); |
164 | 201 |
boolean isAllowedToNotice = ProjectUser.isAllowedToNotice(UserApp.currentUser(), project); |
165 | 202 |
|
203 |
+ if(posting.readme){ |
|
204 |
+ posting.body = BareRepository.readREADME(project); |
|
205 |
+ } |
|
166 | 206 |
return ok(edit.render("post.modify", editForm, posting, number, project, isAllowedToNotice)); |
167 | 207 |
} |
168 | 208 |
|
... | ... | @@ -183,7 +223,11 @@ |
183 | 223 |
|
184 | 224 |
final Posting post = postForm.get(); |
185 | 225 |
final Posting original = Posting.findByNumber(project, number); |
186 |
- |
|
226 |
+ if (post.readme) { |
|
227 |
+ post.setAuthor(UserApp.currentUser()); |
|
228 |
+ commitReadmeFile(project, post); |
|
229 |
+ unmarkAnotherReadmePostingIfExists(project, number); |
|
230 |
+ } |
|
187 | 231 |
Call redirectTo = routes.BoardApp.post(project.owner, project.name, number); |
188 | 232 |
Runnable updatePostingBeforeUpdate = new Runnable() { |
189 | 233 |
@Override |
... | ... | @@ -195,6 +239,14 @@ |
195 | 239 |
return editPosting(original, post, postForm, redirectTo, updatePostingBeforeUpdate); |
196 | 240 |
} |
197 | 241 |
|
242 |
+ private static void unmarkAnotherReadmePostingIfExists(Project project, Long postingNumber) { |
|
243 |
+ Posting previousReadmePosting = Posting.findREADMEPosting(project); |
|
244 |
+ if(previousReadmePosting != null && previousReadmePosting.getNumber() != postingNumber){ |
|
245 |
+ previousReadmePosting.readme = false; |
|
246 |
+ previousReadmePosting.directSave(); |
|
247 |
+ } |
|
248 |
+ } |
|
249 |
+ |
|
198 | 250 |
/** |
199 | 251 |
* @see controllers.AbstractPostingApp#delete(play.db.ebean.Model, models.resource.Resource, play.mvc.Call) |
200 | 252 |
*/ |
--- app/models/AbstractPosting.java
+++ app/models/AbstractPosting.java
... | ... | @@ -139,6 +139,14 @@ |
139 | 139 |
updateMention(); |
140 | 140 |
} |
141 | 141 |
|
142 |
+ /** |
|
143 |
+ * use EBean save functionality directly |
|
144 |
+ * to prevent occurring select table lock |
|
145 |
+ */ |
|
146 |
+ public void directSave(){ |
|
147 |
+ updateMention(); |
|
148 |
+ super.save(); |
|
149 |
+ } |
|
142 | 150 |
|
143 | 151 |
public void updateNumber() { |
144 | 152 |
number = increaseNumber(); |
--- app/models/Posting.java
+++ app/models/Posting.java
... | ... | @@ -22,6 +22,7 @@ |
22 | 22 |
public static final Finder<Long, Posting> finder = new Finder<>(Long.class, Posting.class); |
23 | 23 |
|
24 | 24 |
public boolean notice; |
25 |
+ public boolean readme; |
|
25 | 26 |
|
26 | 27 |
@OneToMany(cascade = CascadeType.ALL) |
27 | 28 |
public List<PostingComment> comments; |
... | ... | @@ -92,4 +93,20 @@ |
92 | 93 |
public static int countPostings(Project project) { |
93 | 94 |
return finder.where().eq("project", project).findRowCount(); |
94 | 95 |
} |
96 |
+ |
|
97 |
+ /** |
|
98 |
+ * use EBean save functionality directly |
|
99 |
+ * to prevent occurring select table lock |
|
100 |
+ */ |
|
101 |
+ public void directSave(){ |
|
102 |
+ super.directSave(); |
|
103 |
+ } |
|
104 |
+ |
|
105 |
+ public static Posting findREADMEPosting(Project project) { |
|
106 |
+ return Posting.finder.where() |
|
107 |
+ .eq("project.id", project.id) |
|
108 |
+ .add(eq("readme", true)) |
|
109 |
+ .findUnique(); |
|
110 |
+ } |
|
111 |
+ |
|
95 | 112 |
} |
+++ app/playRepository/BareCommit.java
... | ... | @@ -0,0 +1,227 @@ |
1 | +/** | |
2 | + * Yobi, Project Hosting SW | |
3 | + * | |
4 | + * Copyright 2014 NAVER Corp. | |
5 | + * http://yobi.io | |
6 | + * | |
7 | + * @Author Suwon Chae | |
8 | + * | |
9 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
10 | + * you may not use this file except in compliance with the License. | |
11 | + * You may obtain a copy of the License at | |
12 | + * | |
13 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
14 | + * | |
15 | + * Unless required by applicable law or agreed to in writing, software | |
16 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
17 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
18 | + * See the License for the specific language governing permissions and | |
19 | + * limitations under the License. | |
20 | + */ | |
21 | +package playRepository; | |
22 | + | |
23 | +import models.Project; | |
24 | +import models.User; | |
25 | +import org.eclipse.jgit.lib.*; | |
26 | +import org.eclipse.jgit.revwalk.RevCommit; | |
27 | +import org.eclipse.jgit.revwalk.RevWalk; | |
28 | +import org.eclipse.jgit.treewalk.CanonicalTreeParser; | |
29 | + | |
30 | +import java.io.File; | |
31 | +import java.io.IOException; | |
32 | +import java.io.RandomAccessFile; | |
33 | +import java.nio.channels.FileChannel; | |
34 | +import java.nio.channels.FileLock; | |
35 | +import java.nio.channels.OverlappingFileLockException; | |
36 | + | |
37 | +import static org.eclipse.jgit.lib.Constants.HEAD; | |
38 | +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; | |
39 | + | |
40 | +public class BareCommit { | |
41 | + private PersonIdent personIdent; | |
42 | + private Repository repository; | |
43 | + private String commitMessage; | |
44 | + private ObjectInserter objectInserter; | |
45 | + | |
46 | + private File file; | |
47 | + private String refName; | |
48 | + private ObjectId headObjectId; | |
49 | + | |
50 | + public BareCommit(Project project, User user) { | |
51 | + this.repository = BareRepository.getRepository(project); | |
52 | + this.personIdent = new PersonIdent(user.name, user.email); | |
53 | + } | |
54 | + | |
55 | + /** | |
56 | + * Commit to bare repository with filename and text contents | |
57 | + * | |
58 | + * @param fileNameWithPath | |
59 | + * @param contents | |
60 | + * @param message | |
61 | + */ | |
62 | + public void commitTextFile(String fileNameWithPath, String contents, String message) throws IOException { | |
63 | + this.file = new File(fileNameWithPath); | |
64 | + setCommitMessage(message); | |
65 | + if (this.refName == null) { | |
66 | + setRefName(HEAD); | |
67 | + } | |
68 | + | |
69 | + RefHeadFileLock refHeadFileLock = new RefHeadFileLock().invoke(this.refName); | |
70 | + try { | |
71 | + this.objectInserter = this.repository.newObjectInserter(); | |
72 | + refUpdate(createCommitWithNewTree(createGitObjectWithText(contents)), refName); | |
73 | + } catch (OverlappingFileLockException e) { | |
74 | + play.Logger.error("Overlapping File Lock Error: " + e.getMessage()); | |
75 | + } finally { | |
76 | + objectInserter.release(); | |
77 | + repository.close(); | |
78 | + refHeadFileLock.release(); | |
79 | + } | |
80 | + } | |
81 | + | |
82 | + private boolean noHeadRef() { | |
83 | + if(this.headObjectId == null) { | |
84 | + return true; | |
85 | + } | |
86 | + return this.headObjectId.equals(ObjectId.zeroId()); | |
87 | + } | |
88 | + | |
89 | + private ObjectId createCommitWithNewTree(ObjectId targetTextFileObjectId) throws IOException { | |
90 | + return objectInserter.insert(buildCommitWith(file.getName(), targetTextFileObjectId)); | |
91 | + } | |
92 | + | |
93 | + private CommitBuilder buildCommitWith(String fileName, ObjectId fileObjectId) throws IOException { | |
94 | + CommitBuilder commit = new CommitBuilder(); | |
95 | + commit.setTreeId(createTreeWith(fileName, fileObjectId)); | |
96 | + if (!noHeadRef()) { | |
97 | + commit.setParentId(this.headObjectId); | |
98 | + } | |
99 | + commit.setAuthor(getPersonIdent()); | |
100 | + commit.setCommitter(getPersonIdent()); | |
101 | + commit.setMessage(getCommitMessage()); | |
102 | + return commit; | |
103 | + } | |
104 | + | |
105 | + private ObjectId createTreeWith(String fileName, ObjectId fileObjectId) throws IOException { | |
106 | + if (noHeadRef()){ | |
107 | + return objectInserter.insert(newTreeWith(fileName, fileObjectId)); | |
108 | + } else { | |
109 | + return objectInserter.insert(rebuildExistingTreeWith(fileName, fileObjectId)); | |
110 | + } | |
111 | + } | |
112 | + | |
113 | + private TreeFormatter newTreeWith(String fileName, ObjectId fileObjectId) { | |
114 | + TreeFormatter formatter = new TreeFormatter(); | |
115 | + formatter.append(fileName, FileMode.REGULAR_FILE, fileObjectId); | |
116 | + return formatter; | |
117 | + } | |
118 | + | |
119 | + private TreeFormatter rebuildExistingTreeWith(String fileName, ObjectId fileObjectId) throws IOException { | |
120 | + TreeFormatter formatter = new TreeFormatter(); | |
121 | + CanonicalTreeParser treeParser = getCanonicalTreeParser(this.repository); | |
122 | + formatter.append(fileName, FileMode.REGULAR_FILE, fileObjectId); | |
123 | + while(!treeParser.eof()){ | |
124 | + String entryName = new String(treeParser.getEntryPathBuffer(), 0, treeParser.getEntryPathLength()); | |
125 | + if (!entryName.equals(fileName)){ | |
126 | + formatter.append(entryName | |
127 | + , treeParser.getEntryFileMode() | |
128 | + , treeParser.getEntryObjectId()); | |
129 | + } | |
130 | + treeParser = treeParser.next(); | |
131 | + } | |
132 | + return formatter; | |
133 | + } | |
134 | + | |
135 | + private CanonicalTreeParser getCanonicalTreeParser(Repository repository) throws IOException { | |
136 | + RevWalk revWalk = new RevWalk(repository); | |
137 | + RevCommit commit = revWalk.parseCommit(this.headObjectId); | |
138 | + return new CanonicalTreeParser(new byte[]{}, revWalk.getObjectReader(), commit.getTree().getId()); | |
139 | + } | |
140 | + | |
141 | + private ObjectId createGitObjectWithText(String contents) throws IOException { | |
142 | + return objectInserter.insert(OBJ_BLOB, contents.getBytes(), 0, contents.getBytes().length); | |
143 | + } | |
144 | + | |
145 | + private void refUpdate(ObjectId commitId, String refName) throws IOException { | |
146 | + RefUpdate ru = this.repository.updateRef(refName); | |
147 | + ru.setForceUpdate(false); | |
148 | + ru.setRefLogIdent(getPersonIdent()); | |
149 | + ru.setNewObjectId(commitId); | |
150 | + if(hasOldCommit(refName)){ | |
151 | + ru.setExpectedOldObjectId(getCurrentMomentHeadObjectId()); | |
152 | + } | |
153 | + ru.setRefLogMessage(getCommitMessage(), false); | |
154 | + ru.update(); | |
155 | + } | |
156 | + | |
157 | + private boolean hasOldCommit(String refName) throws IOException { | |
158 | + return this.repository.getRef(refName).getObjectId() != null; | |
159 | + } | |
160 | + | |
161 | + private PersonIdent getPersonIdent() { | |
162 | + if (this.personIdent == null) { | |
163 | + this.personIdent = new PersonIdent(this.repository); | |
164 | + } | |
165 | + return personIdent; | |
166 | + } | |
167 | + | |
168 | + private String getCommitMessage() { | |
169 | + if(this.commitMessage == null){ | |
170 | + return "Update " + this.file.getName(); | |
171 | + } | |
172 | + return commitMessage; | |
173 | + } | |
174 | + | |
175 | + public void setCommitMessage(String commitMessage) { | |
176 | + this.commitMessage = commitMessage; | |
177 | + } | |
178 | + | |
179 | + public void setHeadObjectId(String refName) throws IOException { | |
180 | + if(this.repository.getRef(refName).getObjectId() == null){ | |
181 | + this.headObjectId = ObjectId.zeroId(); | |
182 | + } else { | |
183 | + this.headObjectId = this.repository.getRef(refName).getObjectId(); | |
184 | + } | |
185 | + } | |
186 | + | |
187 | + public ObjectId getCurrentMomentHeadObjectId() throws IOException { | |
188 | + if( this.repository.getRef(refName).getObjectId() == null ){ | |
189 | + return ObjectId.zeroId(); | |
190 | + } else { | |
191 | + return this.repository.getRef(refName).getObjectId(); | |
192 | + } | |
193 | + } | |
194 | + | |
195 | + public void setRefName(String refName){ | |
196 | + this.refName = refName; | |
197 | + } | |
198 | + | |
199 | + private class RefHeadFileLock { | |
200 | + private FileChannel channel; | |
201 | + private FileLock lock; | |
202 | + private File refHeadFile; | |
203 | + | |
204 | + public RefHeadFileLock invoke(String refName) throws IOException { | |
205 | + refHeadFile = new File(repository.getDirectory().getPath(), | |
206 | + repository.getRef(refName).getLeaf().getName()); | |
207 | + if(refHeadFile.exists()){ | |
208 | + channel = new RandomAccessFile(refHeadFile, "rw").getChannel(); | |
209 | + lock = channel.lock(); | |
210 | + } | |
211 | + setHeadObjectId(refName); | |
212 | + return this; | |
213 | + } | |
214 | + | |
215 | + public void release() { | |
216 | + try { | |
217 | + if(refHeadFile.exists()) { | |
218 | + if(lock != null) lock.release(); | |
219 | + if(channel != null) channel.close(); | |
220 | + } | |
221 | + } catch (IOException e) { | |
222 | + e.printStackTrace(); | |
223 | + play.Logger.error(e.getMessage()); | |
224 | + } | |
225 | + } | |
226 | + } | |
227 | +} |
+++ app/playRepository/BareRepository.java
... | ... | @@ -0,0 +1,118 @@ |
1 | +/** | |
2 | + * Yobi, Project Hosting SW | |
3 | + * | |
4 | + * Copyright 2014 NAVER Corp. | |
5 | + * http://yobi.io | |
6 | + * | |
7 | + * @Author Suwon Chae | |
8 | + * | |
9 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
10 | + * you may not use this file except in compliance with the License. | |
11 | + * You may obtain a copy of the License at | |
12 | + * | |
13 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
14 | + * | |
15 | + * Unless required by applicable law or agreed to in writing, software | |
16 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
17 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
18 | + * See the License for the specific language governing permissions and | |
19 | + * limitations under the License. | |
20 | + */ | |
21 | +package playRepository; | |
22 | + | |
23 | +import models.Project; | |
24 | +import org.eclipse.jgit.lib.*; | |
25 | +import org.eclipse.jgit.revwalk.RevCommit; | |
26 | +import org.eclipse.jgit.revwalk.RevTree; | |
27 | +import org.eclipse.jgit.revwalk.RevWalk; | |
28 | +import org.eclipse.jgit.treewalk.TreeWalk; | |
29 | +import org.eclipse.jgit.treewalk.filter.OrTreeFilter; | |
30 | +import org.eclipse.jgit.treewalk.filter.PathFilter; | |
31 | +import org.eclipse.jgit.treewalk.filter.TreeFilter; | |
32 | +import org.eclipse.jgit.util.FS; | |
33 | + | |
34 | +import javax.servlet.ServletException; | |
35 | +import java.io.IOException; | |
36 | + | |
37 | +import static org.eclipse.jgit.lib.Constants.HEAD; | |
38 | + | |
39 | +public class BareRepository { | |
40 | + /** | |
41 | + * read project README file with readme filename filter from repository | |
42 | + * | |
43 | + * @param project | |
44 | + * @return | |
45 | + */ | |
46 | + public static String readREADME(Project project){ | |
47 | + Repository repository; | |
48 | + ObjectLoader loader = null; | |
49 | + repository = getRepository(project); | |
50 | + try { | |
51 | + loader = repository.open(getFirstFoundREADMEfileObjectId(repository)); | |
52 | + } catch (IOException e) { | |
53 | + e.printStackTrace(); | |
54 | + play.Logger.error(e.getMessage()); | |
55 | + } | |
56 | + return new String(loader.getCachedBytes()); | |
57 | + } | |
58 | + | |
59 | + public static Repository getRepository(Project project){ | |
60 | + Repository repository = null; | |
61 | + try { | |
62 | + RepositoryCache.FileKey fileKey = RepositoryCache.FileKey.exact( | |
63 | + RepositoryService.getRepository(project).getDirectory(), FS.DETECTED); | |
64 | + repository = fileKey.open(false); | |
65 | + } catch (ServletException | IOException e) { | |
66 | + e.printStackTrace(); | |
67 | + play.Logger.error(e.getMessage()); | |
68 | + } | |
69 | + return repository; | |
70 | + } | |
71 | + | |
72 | + public static ObjectId getFileObjectId(Repository repository, String fileNameWithPath) throws IOException { | |
73 | + TreeWalk treeWalk = new TreeWalk(repository); | |
74 | + RevTree revTree = getRevTreeFromRef(repository, repository.getRef(HEAD)); | |
75 | + if( revTree == null ){ | |
76 | + return null; | |
77 | + } | |
78 | + treeWalk.addTree(revTree); | |
79 | + treeWalk.setRecursive(false); | |
80 | + treeWalk.setFilter(PathFilter.create(fileNameWithPath)); | |
81 | + return treeWalk.getObjectId(0); | |
82 | + } | |
83 | + | |
84 | + private static ObjectId getFirstFoundREADMEfileObjectId(Repository repository) throws IOException { | |
85 | + TreeWalk treeWalk = new TreeWalk(repository); | |
86 | + RevTree revTree = getRevTreeFromRef(repository, repository.getRef(HEAD)); | |
87 | + if( revTree != null ){ | |
88 | + treeWalk.addTree(revTree); | |
89 | + } | |
90 | + treeWalk.setRecursive(false); | |
91 | + treeWalk.setFilter(OrTreeFilter.create(READMEFileNameFilter())); | |
92 | + | |
93 | + if (!treeWalk.next()) { | |
94 | + play.Logger.info("no tree or no README file found"); | |
95 | + throw new IllegalStateException("Did not find expected file 'README.md'"); | |
96 | + } | |
97 | + | |
98 | + return treeWalk.getObjectId(0); | |
99 | + } | |
100 | + | |
101 | + private static RevTree getRevTreeFromRef(Repository repository, Ref ref) throws IOException { | |
102 | + if(ref.getObjectId() == null){ | |
103 | + return null; | |
104 | + } | |
105 | + RevWalk revWalk = new RevWalk(repository); | |
106 | + RevCommit commit = revWalk.parseCommit(ref.getObjectId()); | |
107 | + return commit.getTree(); | |
108 | + } | |
109 | + | |
110 | + private static TreeFilter[] READMEFileNameFilter() { | |
111 | + TreeFilter[] filters = new TreeFilter[4]; | |
112 | + filters[0] = PathFilter.create("README.md"); | |
113 | + filters[1] = PathFilter.create("readme.md"); | |
114 | + filters[2] = PathFilter.create("README.markdown"); | |
115 | + filters[3] = PathFilter.create("readme.markdown"); | |
116 | + return filters; | |
117 | + } | |
118 | +} |
--- app/playRepository/GitRepository.java
+++ app/playRepository/GitRepository.java
... | ... | @@ -204,13 +204,9 @@ |
204 | 204 |
TreeWalk treeWalk = new TreeWalk(repository); |
205 | 205 |
treeWalk.addTree(revTree); |
206 | 206 |
|
207 |
- while (treeWalk.next()) { |
|
208 |
- if (treeWalk.getPathString().equals(path) && !treeWalk.isSubtree()) { |
|
209 |
- return true; |
|
210 |
- } |
|
211 |
- } |
|
212 |
- |
|
213 |
- return false; |
|
207 |
+ treeWalk.setRecursive(true); |
|
208 |
+ treeWalk.setFilter(PathFilter.create(path)); |
|
209 |
+ return treeWalk.next(); |
|
214 | 210 |
} |
215 | 211 |
|
216 | 212 |
@Override |
... | ... | @@ -1929,4 +1925,9 @@ |
1929 | 1925 |
return false; |
1930 | 1926 |
} |
1931 | 1927 |
} |
1928 |
+ |
|
1929 |
+ @Override |
|
1930 |
+ public File getDirectory() { |
|
1931 |
+ return this.repository.getDirectory(); |
|
1932 |
+ } |
|
1932 | 1933 |
} |
--- app/playRepository/PlayRepository.java
+++ app/playRepository/PlayRepository.java
... | ... | @@ -24,9 +24,11 @@ |
24 | 24 |
|
25 | 25 |
import org.codehaus.jackson.node.ObjectNode; |
26 | 26 |
import org.eclipse.jgit.api.errors.GitAPIException; |
27 |
+import org.eclipse.jgit.lib.Repository; |
|
27 | 28 |
import org.tigris.subversion.javahl.ClientException; |
28 | 29 |
import org.tmatesoft.svn.core.SVNException; |
29 | 30 |
|
31 |
+import java.io.File; |
|
30 | 32 |
import java.io.IOException; |
31 | 33 |
import java.util.List; |
32 | 34 |
|
... | ... | @@ -76,4 +78,6 @@ |
76 | 78 |
boolean isEmpty(); |
77 | 79 |
|
78 | 80 |
boolean move(String srcProjectOwner, String srcProjectName, String desrProjectOwner, String destProjectName); |
81 |
+ |
|
82 |
+ public File getDirectory(); |
|
79 | 83 |
} |
--- app/playRepository/SVNRepository.java
+++ app/playRepository/SVNRepository.java
... | ... | @@ -20,7 +20,6 @@ |
20 | 20 |
*/ |
21 | 21 |
package playRepository; |
22 | 22 |
|
23 |
-import controllers.ProjectApp; |
|
24 | 23 |
import controllers.UserApp; |
25 | 24 |
import controllers.routes; |
26 | 25 |
import models.Project; |
... | ... | @@ -439,4 +438,9 @@ |
439 | 438 |
return false; |
440 | 439 |
} |
441 | 440 |
} |
441 |
+ |
|
442 |
+ @Override |
|
443 |
+ public File getDirectory() { |
|
444 |
+ return new File(getRepoPrefix() + ownerName + "/" + projectName); |
|
445 |
+ } |
|
442 | 446 |
} |
+++ app/utils/HtmlUtil.java
... | ... | @@ -0,0 +1,45 @@ |
1 | +package utils; | |
2 | + | |
3 | +import org.apache.commons.lang.StringUtils; | |
4 | + | |
5 | +/** | |
6 | + * Yobi, Project Hosting SW | |
7 | + * | |
8 | + * Copyright 2014 NAVER Corp. | |
9 | + * http://yobi.io | |
10 | + * | |
11 | + * @Author Suwon Chae | |
12 | + * | |
13 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
14 | + * you may not use this file except in compliance with the License. | |
15 | + * You may obtain a copy of the License at | |
16 | + * | |
17 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
18 | + * | |
19 | + * Unless required by applicable law or agreed to in writing, software | |
20 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
21 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
22 | + * See the License for the specific language governing permissions and | |
23 | + * limitations under the License. | |
24 | + */ | |
25 | +public class HtmlUtil { | |
26 | + public static String boolToCheckedString(boolean bool){ | |
27 | + if (bool == true) { | |
28 | + return "checked"; | |
29 | + } else { | |
30 | + return ""; | |
31 | + } | |
32 | + } | |
33 | + | |
34 | + public static String boolToCheckedString(String bool){ | |
35 | + if (StringUtils.isBlank(bool)){ | |
36 | + return ""; | |
37 | + } | |
38 | + if (bool.equals("true")) { | |
39 | + return "checked"; | |
40 | + } else { | |
41 | + return ""; | |
42 | + } | |
43 | + } | |
44 | + | |
45 | +} |
--- app/views/board/create.scala.html
+++ app/views/board/create.scala.html
... | ... | @@ -18,12 +18,18 @@ |
18 | 18 |
* See the License for the specific language governing permissions and |
19 | 19 |
* limitations under the License. |
20 | 20 |
**@ |
21 |
-@(title:String, form:Form[Posting], project:Project, isAllowedToNotice:Boolean) |
|
21 |
+@(title:String, form:Form[Posting], project:Project, isAllowedToNotice:Boolean, preparedPostBody:String = "") |
|
22 | 22 |
|
23 |
-@import scala.collection.Map |
|
24 | 23 |
@import utils.TemplateHelper._ |
24 |
+@import utils.HtmlUtil._ |
|
25 | 25 |
@import models.enumeration._ |
26 | 26 |
@implicitField = @{ helper.FieldConstructor(simpleForm) } |
27 |
+ |
|
28 |
+@readmeUpdateMessage = @{ |
|
29 |
+ if( request().getQueryString("readme") != null ) { |
|
30 |
+ "Update README.md" |
|
31 |
+ } |
|
32 |
+} |
|
27 | 33 |
|
28 | 34 |
@projectLayout(title, project, utils.MenuType.BOARD) { |
29 | 35 |
@projectMenu(project, utils.MenuType.BOARD, "main-menu-only") |
... | ... | @@ -37,7 +43,7 @@ |
37 | 43 |
</dt> |
38 | 44 |
<dd> |
39 | 45 |
@defining(form.errors().get("title")) { errors => |
40 |
- <input type="text" id="title" name="title" class="zen-mode text title @if(errors != null) {error}" maxlength="250" tabindex="1" /> |
|
46 |
+ <input type="text" id="title" name="title" class="zen-mode text title @if(errors != null) {error}" maxlength="250" tabindex="1" value="@readmeUpdateMessage"/> |
|
41 | 47 |
@if(errors != null) { |
42 | 48 |
<div class="message"> |
43 | 49 |
@for(error <- errors) { |
... | ... | @@ -49,13 +55,18 @@ |
49 | 55 |
</dd> |
50 | 56 |
|
51 | 57 |
<dd style="position: relative;"> |
52 |
- @common.editor("body", "", "tabindex=2") |
|
58 |
+ @common.editor("body", preparedPostBody, "tabindex=2") |
|
53 | 59 |
</dd> |
54 | 60 |
|
55 | 61 |
@if(isAllowedToNotice){ |
56 | 62 |
<dd class="right-txt"> |
57 | 63 |
<input type="checkbox" id="notice" name="notice" class="checkbox" /><!-- |
58 | 64 |
--><label for="notice" class="label-public">@Messages("post.notice.label")</label> |
65 |
+ @if(project.isGit && request().getQueryString("readme") != null ) { |
|
66 |
+ <input type="checkbox" id="readme" name="readme" class="checkbox" @boolToCheckedString(request().getQueryString("readme")) |
|
67 |
+ /><!-- |
|
68 |
+ --><label for="readme" class="label-public">@Messages("post.readmefy")</label> |
|
69 |
+ } |
|
59 | 70 |
</dd> |
60 | 71 |
} |
61 | 72 |
</dl> |
--- app/views/board/edit.scala.html
+++ app/views/board/edit.scala.html
... | ... | @@ -24,6 +24,7 @@ |
24 | 24 |
@import utils.TemplateHelper._ |
25 | 25 |
@import models.enumeration.ResourceType |
26 | 26 |
@implicitField = @{ helper.FieldConstructor(simpleForm) } |
27 |
+@import utils.HtmlUtil._ |
|
27 | 28 |
|
28 | 29 |
@projectLayout(title, project, utils.MenuType.BOARD) { |
29 | 30 |
@projectMenu(project, utils.MenuType.BOARD, "main-menu-only") |
... | ... | @@ -57,6 +58,10 @@ |
57 | 58 |
<dd class="right-txt"> |
58 | 59 |
<input type="checkbox" id="notice" name="notice" class="checkbox" @toHtmlArgs(args) @(if(value.equals(Some("true"))) "checked" else "")/><!-- |
59 | 60 |
--><label for="notice" class="label-public">@Messages("post.notice.label")</label> |
61 |
+ @if(project.isGit){ |
|
62 |
+ <input type="checkbox" id="readme" name="readme" class="checkbox" @boolToCheckedString(posting.readme)/> |
|
63 |
+ <label for="readme" class="label-public">@Messages("post.readmefy")</label> |
|
64 |
+ } |
|
60 | 65 |
</dd> |
61 | 66 |
} |
62 | 67 |
} |
--- app/views/board/partial_list.scala.html
+++ app/views/board/partial_list.scala.html
... | ... | @@ -34,6 +34,9 @@ |
34 | 34 |
<div class="title-wrap"> |
35 | 35 |
@if(post.notice){ |
36 | 36 |
<span class="label label-notice">@Messages("post.notice")</span> |
37 |
+ } |
|
38 |
+ @if(post.readme){ |
|
39 |
+ <span class="label label-important">README</span> |
|
37 | 40 |
} else { |
38 | 41 |
<span class="post-id">@post.getNumber</span> |
39 | 42 |
} |
--- app/views/project/partial_readme.scala.html
+++ app/views/project/partial_readme.scala.html
... | ... | @@ -19,20 +19,28 @@ |
19 | 19 |
* limitations under the License. |
20 | 20 |
**@ |
21 | 21 |
@(project: Project) |
22 |
+@import utils.AccessControl._ |
|
23 |
+@import models.enumeration.ResourceType |
|
22 | 24 |
|
23 | 25 |
<div class="bubble-wrap gray readme"> |
24 | 26 |
@if(project.readme == null){ |
25 | 27 |
<p class="default"> |
26 |
- <span class="v"></span> |
|
27 | 28 |
@if(project.vcs.equals("GIT")) { |
28 |
- <span>@Messages("project.readme")</span> |
|
29 |
+ <span>@Messages("project.readme")</span><br/><br/> |
|
30 |
+ @if(isProjectResourceCreatable(UserApp.currentUser(), project, ResourceType.COMMIT)) { |
|
31 |
+ <a href="@routes.BoardApp.newPostForm(project.owner, project.name)?readme=true" class="ybtn">@Messages("project.readme.create")</a> |
|
32 |
+ } |
|
29 | 33 |
} else { |
30 | 34 |
<span>@Messages("project.svn.readme")</span> |
31 | 35 |
} |
32 | 36 |
</p> |
33 | 37 |
} else { |
34 | 38 |
<div class="readme-wrap"> |
35 |
- <header><i class="yobicon-book-open"></i><strong> @project.getReadmeFileName</strong></header> |
|
39 |
+ <header><i class="yobicon-book-open"></i><strong> @project.getReadmeFileName</strong> |
|
40 |
+ @if(isProjectResourceCreatable(UserApp.currentUser(), project, ResourceType.COMMIT)) { |
|
41 |
+ <a href="@routes.BoardApp.newPostForm(project.owner, project.name)?readme=true" class="ybtn">edit</a> |
|
42 |
+ } |
|
43 |
+ </header> |
|
36 | 44 |
<div class="readme-body markdown-wrap markdown-before" markdown="true">@project.readme</div> |
37 | 45 |
</div> |
38 | 46 |
} |
+++ conf/evolutions/default/84.sql
... | ... | @@ -0,0 +1,8 @@ |
1 | +# --- !Ups | |
2 | + | |
3 | +alter table posting add column readme boolean; | |
4 | +update posting set readme = false; | |
5 | + | |
6 | +# --- !Downs | |
7 | + | |
8 | +alter table posting drop column readme; |
--- conf/messages
+++ conf/messages
... | ... | @@ -406,6 +406,7 @@ |
406 | 406 |
post.popup.fileAttach.hasMissing = {0} attachment files are missing. |
407 | 407 |
post.popup.fileAttach.hasMissing.description = It may caused by server keep time of temporary upload file ({1} min). Please upload again. |
408 | 408 |
post.popup.fileAttach.title = Select files |
409 |
+post.readmefy= make it a README file |
|
409 | 410 |
post.unwatch.start = Notifications about this post has been muted |
410 | 411 |
post.update.error = Errors in input values. |
411 | 412 |
post.watch.start = You will receive notifications about this post |
... | ... | @@ -504,6 +505,7 @@ |
504 | 505 |
project.public = PUBLIC |
505 | 506 |
project.public.notice = Anonymous users are available to access the project. |
506 | 507 |
project.readme = README.md will be shown here if you add it into the code repository''s root directory. |
508 |
+project.readme.create = create README |
|
507 | 509 |
project.recently.visited = Recently visited |
508 | 510 |
project.reviewer.count = Reviewer |
509 | 511 |
project.reviewer.count.description = ( ) of reviewers is required to accept pull-request. |
--- conf/messages.ko
+++ conf/messages.ko
... | ... | @@ -406,6 +406,7 @@ |
406 | 406 |
post.popup.fileAttach.hasMissing = 첨부되지 못한 파일이 {0}개 있습니다. |
407 | 407 |
post.popup.fileAttach.hasMissing.description = 업로드 후 {1}분이 지나도록 글 작성을 완료하지 않은 경우 이 문제가 발생할 수 있습니다. 파일을 다시 첨부해 주세요. |
408 | 408 |
post.popup.fileAttach.title = 첨부파일 선택 |
409 |
+post.readmefy= 프로젝트 README 파일로 만듭니다 |
|
409 | 410 |
post.unwatch.start = 이제 이 글에 관한 알림을 받지 않습니다 |
410 | 411 |
post.update.error = 입력값 오류 |
411 | 412 |
post.watch.start = 이제 이 글에 관한 알림을 받습니다 |
... | ... | @@ -504,6 +505,7 @@ |
504 | 505 |
project.public = 공개 |
505 | 506 |
project.public.notice = 모든 사람이 인증절차 없이 접근할 수 있습니다. 일부 기능은 로그인이 필요할 수 있습니다. |
506 | 507 |
project.readme = 프로젝트에 대한 설명을 README.md 파일로 작성해서 코드저장소 루트 디렉토리에 추가하면 이 곳에 나타납니다. |
508 |
+project.readme.create = README 만들기 |
|
507 | 509 |
project.recently.visited = 최근 방문한 프로젝트 |
508 | 510 |
project.reviewer.count.description = 명이 리뷰를 완료하면 코드를 받을 수 있습니다. |
509 | 511 |
project.reviewer.count.disable = 사용 안함 |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?