James Moger
2012-11-01 7ba85bfa11c7fcab21ada61650fe30763aafd7b0
commit | author | age
746aaf 1 /*
JM 2  * Copyright 2012 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.build;
17
18 import java.io.File;
19 import java.io.FileInputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.text.MessageFormat;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Set;
26 import java.util.TreeSet;
27
28 import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
29 import org.eclipse.jgit.api.errors.JGitInternalException;
30 import org.eclipse.jgit.dircache.DirCache;
31 import org.eclipse.jgit.dircache.DirCacheBuilder;
32 import org.eclipse.jgit.dircache.DirCacheEntry;
5d9bd7 33 import org.eclipse.jgit.internal.JGitText;
746aaf 34 import org.eclipse.jgit.lib.CommitBuilder;
JM 35 import org.eclipse.jgit.lib.Constants;
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.lib.RepositoryCache.FileKey;
44 import org.eclipse.jgit.revwalk.RevCommit;
45 import org.eclipse.jgit.revwalk.RevWalk;
46 import org.eclipse.jgit.storage.file.FileRepository;
47 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
48 import org.eclipse.jgit.treewalk.TreeWalk;
49 import org.eclipse.jgit.util.FS;
50
51 import com.beust.jcommander.JCommander;
52 import com.beust.jcommander.Parameter;
53 import com.beust.jcommander.ParameterException;
54 import com.beust.jcommander.Parameters;
55 import com.gitblit.models.RefModel;
56 import com.gitblit.utils.JGitUtils;
57 import com.gitblit.utils.StringUtils;
58
59 /**
60  * Creates or updates a gh-pages branch with the specified content.
61  * 
62  * @author James Moger
63  * 
64  */
65 public class BuildGhPages {
66
67     public static void main(String[] args) {
68         Params params = new Params();
69         JCommander jc = new JCommander(params);
70         try {
71             jc.parse(args);
72         } catch (ParameterException t) {
73             System.err.println(t.getMessage());
74             jc.usage();
75         }
76
77         File source = new File(params.sourceFolder);
78         String ghpages = "refs/heads/gh-pages";
79         try {            
80             File gitDir = FileKey.resolve(new File(params.repositoryFolder), FS.DETECTED);
81             Repository repository = new FileRepository(gitDir);
82
83             RefModel issuesBranch = JGitUtils.getPagesBranch(repository);
84             if (issuesBranch == null) {
85                 JGitUtils.createOrphanBranch(repository, "gh-pages", null);
86             }
87
88             System.out.println("Updating gh-pages branch...");
89             ObjectId headId = repository.resolve(ghpages + "^{commit}");
90             ObjectInserter odi = repository.newObjectInserter();
91             try {
92                 // Create the in-memory index of the new/updated issue.
93                 DirCache index = createIndex(repository, headId, source, params.obliterate);
94                 ObjectId indexTreeId = index.writeTree(odi);
95
96                 // Create a commit object
97                 PersonIdent author = new PersonIdent("Gitblit", "gitblit@localhost");
98                 CommitBuilder commit = new CommitBuilder();
99                 commit.setAuthor(author);
100                 commit.setCommitter(author);
101                 commit.setEncoding(Constants.CHARACTER_ENCODING);
102                 commit.setMessage("updated pages");
103                 commit.setParentId(headId);
104                 commit.setTreeId(indexTreeId);
105
106                 // Insert the commit into the repository
107                 ObjectId commitId = odi.insert(commit);
108                 odi.flush();
109
110                 RevWalk revWalk = new RevWalk(repository);
111                 try {
112                     RevCommit revCommit = revWalk.parseCommit(commitId);
113                     RefUpdate ru = repository.updateRef(ghpages);
114                     ru.setNewObjectId(commitId);
115                     ru.setExpectedOldObjectId(headId);
116                     ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
117                     Result rc = ru.forceUpdate();
118                     switch (rc) {
119                     case NEW:
120                     case FORCED:
121                     case FAST_FORWARD:
122                         break;
123                     case REJECTED:
124                     case LOCK_FAILURE:
125                         throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD,
126                                 ru.getRef(), rc);
127                     default:
128                         throw new JGitInternalException(MessageFormat.format(
129                                 JGitText.get().updatingRefFailed, ghpages, commitId.toString(), rc));
130                     }
131                 } finally {
132                     revWalk.release();
133                 }
134             } finally {
135                 odi.release();
136             }
137             System.out.println("gh-pages updated.");
138         } catch (Throwable t) {
139             t.printStackTrace();
140         }
141     }
142
143     /**
144      * Creates an in-memory index of the issue change.
145      * 
146      * @param repo
147      * @param headId
148      * @param sourceFolder
149      * @param obliterate
150      *            if true the source folder tree is used as the new tree for
151      *            gh-pages and non-existent files are considered deleted
152      * @return an in-memory index
153      * @throws IOException
154      */
155     private static DirCache createIndex(Repository repo, ObjectId headId, File sourceFolder,
156             boolean obliterate) throws IOException {
157
158         DirCache inCoreIndex = DirCache.newInCore();
159         DirCacheBuilder dcBuilder = inCoreIndex.builder();
160         ObjectInserter inserter = repo.newObjectInserter();
161
162         try {
163             // Add all files to the temporary index
164             Set<String> ignorePaths = new TreeSet<String>();
165             List<File> files = listFiles(sourceFolder);
166             for (File file : files) {
167                 // create an index entry for the file
168                 final DirCacheEntry dcEntry = new DirCacheEntry(StringUtils.getRelativePath(
169                         sourceFolder.getPath(), file.getPath()));
170                 dcEntry.setLength(file.length());
171                 dcEntry.setLastModified(file.lastModified());
172                 dcEntry.setFileMode(FileMode.REGULAR_FILE);
173
174                 // add this entry to the ignore paths set
175                 ignorePaths.add(dcEntry.getPathString());
176
177                 // insert object
178                 InputStream inputStream = new FileInputStream(file);
179                 try {
180                     dcEntry.setObjectId(inserter.insert(Constants.OBJ_BLOB, file.length(),
181                             inputStream));
182                 } finally {
183                     inputStream.close();
184                 }
185
186                 // add to temporary in-core index
187                 dcBuilder.add(dcEntry);
188             }
189
190             if (!obliterate) {
191                 // Traverse HEAD to add all other paths
192                 TreeWalk treeWalk = new TreeWalk(repo);
193                 int hIdx = -1;
194                 if (headId != null)
195                     hIdx = treeWalk.addTree(new RevWalk(repo).parseTree(headId));
196                 treeWalk.setRecursive(true);
197
198                 while (treeWalk.next()) {
199                     String path = treeWalk.getPathString();
200                     CanonicalTreeParser hTree = null;
201                     if (hIdx != -1)
202                         hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class);
203                     if (!ignorePaths.contains(path)) {
204                         // add entries from HEAD for all other paths
205                         if (hTree != null) {
206                             // create a new DirCacheEntry with data retrieved
207                             // from
208                             // HEAD
209                             final DirCacheEntry dcEntry = new DirCacheEntry(path);
210                             dcEntry.setObjectId(hTree.getEntryObjectId());
211                             dcEntry.setFileMode(hTree.getEntryFileMode());
212
213                             // add to temporary in-core index
214                             dcBuilder.add(dcEntry);
215                         }
216                     }
217                 }
218
219                 // release the treewalk
220                 treeWalk.release();
221             }
222             
223             // finish temporary in-core index used for this commit
224             dcBuilder.finish();
225         } finally {
226             inserter.release();
227         }
228         return inCoreIndex;
229     }
230
231     private static List<File> listFiles(File folder) {
232         List<File> files = new ArrayList<File>();
233         for (File file : folder.listFiles()) {
234             if (file.isDirectory()) {
235                 files.addAll(listFiles(file));
236             } else {
237                 files.add(file);
238             }
239         }
240         return files;
241     }
242
243     /**
244      * JCommander Parameters class for BuildGhPages.
245      */
246     @Parameters(separators = " ")
247     private static class Params {
248
249         @Parameter(names = { "--sourceFolder" }, description = "Source folder for pages", required = true)
250         public String sourceFolder;
251
252         @Parameter(names = { "--repository" }, description = "Repository folder", required = true)
253         public String repositoryFolder;
254
255         @Parameter(names = { "--obliterate" }, description = "Replace gh-pages tree with only the content in your sourcefolder")
256         public boolean obliterate;
257
258     }
259 }