summaryrefslogtreecommitdiffstats
path: root/src/com/gitblit/build/BuildGhPages.java
blob: 5982ac30b94e4c69545f03a4891db52b4fef6826 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/*
 * Copyright 2012 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.build;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
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.lib.RepositoryCache.FileKey;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepository;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.FS;

import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameters;
import com.gitblit.models.RefModel;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;

/**
 * Creates or updates a gh-pages branch with the specified content.
 * 
 * @author James Moger
 * 
 */
public class BuildGhPages {

	public static void main(String[] args) {
		Params params = new Params();
		JCommander jc = new JCommander(params);
		try {
			jc.parse(args);
		} catch (ParameterException t) {
			System.err.println(t.getMessage());
			jc.usage();
		}

		File source = new File(params.sourceFolder);
		String ghpages = "refs/heads/gh-pages";
		try {			
			File gitDir = FileKey.resolve(new File(params.repositoryFolder), FS.DETECTED);
			Repository repository = new FileRepository(gitDir);

			RefModel issuesBranch = JGitUtils.getPagesBranch(repository);
			if (issuesBranch == null) {
				JGitUtils.createOrphanBranch(repository, "gh-pages", null);
			}

			System.out.println("Updating gh-pages branch...");
			ObjectId headId = repository.resolve(ghpages + "^{commit}");
			ObjectInserter odi = repository.newObjectInserter();
			try {
				// Create the in-memory index of the new/updated issue.
				DirCache index = createIndex(repository, headId, source, params.obliterate);
				ObjectId indexTreeId = index.writeTree(odi);

				// Create a commit object
				PersonIdent author = new PersonIdent("Gitblit", "gitblit@localhost");
				CommitBuilder commit = new CommitBuilder();
				commit.setAuthor(author);
				commit.setCommitter(author);
				commit.setEncoding(Constants.CHARACTER_ENCODING);
				commit.setMessage("updated pages");
				commit.setParentId(headId);
				commit.setTreeId(indexTreeId);

				// Insert the commit into the repository
				ObjectId commitId = odi.insert(commit);
				odi.flush();

				RevWalk revWalk = new RevWalk(repository);
				try {
					RevCommit revCommit = revWalk.parseCommit(commitId);
					RefUpdate ru = repository.updateRef(ghpages);
					ru.setNewObjectId(commitId);
					ru.setExpectedOldObjectId(headId);
					ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
					Result rc = ru.forceUpdate();
					switch (rc) {
					case NEW:
					case FORCED:
					case FAST_FORWARD:
						break;
					case REJECTED:
					case LOCK_FAILURE:
						throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD,
								ru.getRef(), rc);
					default:
						throw new JGitInternalException(MessageFormat.format(
								JGitText.get().updatingRefFailed, ghpages, commitId.toString(), rc));
					}
				} finally {
					revWalk.release();
				}
			} finally {
				odi.release();
			}
			System.out.println("gh-pages updated.");
		} catch (Throwable t) {
			t.printStackTrace();
		}
	}

	/**
	 * Creates an in-memory index of the issue change.
	 * 
	 * @param repo
	 * @param headId
	 * @param sourceFolder
	 * @param obliterate
	 *            if true the source folder tree is used as the new tree for
	 *            gh-pages and non-existent files are considered deleted
	 * @return an in-memory index
	 * @throws IOException
	 */
	private static DirCache createIndex(Repository repo, ObjectId headId, File sourceFolder,
			boolean obliterate) throws IOException {

		DirCache inCoreIndex = DirCache.newInCore();
		DirCacheBuilder dcBuilder = inCoreIndex.builder();
		ObjectInserter inserter = repo.newObjectInserter();

		try {
			// Add all files to the temporary index
			Set<String> ignorePaths = new TreeSet<String>();
			List<File> files = listFiles(sourceFolder);
			for (File file : files) {
				// create an index entry for the file
				final DirCacheEntry dcEntry = new DirCacheEntry(StringUtils.getRelativePath(
						sourceFolder.getPath(), file.getPath()));
				dcEntry.setLength(file.length());
				dcEntry.setLastModified(file.lastModified());
				dcEntry.setFileMode(FileMode.REGULAR_FILE);

				// add this entry to the ignore paths set
				ignorePaths.add(dcEntry.getPathString());

				// insert object
				InputStream inputStream = new FileInputStream(file);
				try {
					dcEntry.setObjectId(inserter.insert(Constants.OBJ_BLOB, file.length(),
							inputStream));
				} finally {
					inputStream.close();
				}

				// add to temporary in-core index
				dcBuilder.add(dcEntry);
			}

			if (!obliterate) {
				// Traverse HEAD to add all other paths
				TreeWalk treeWalk = new TreeWalk(repo);
				int hIdx = -1;
				if (headId != null)
					hIdx = treeWalk.addTree(new RevWalk(repo).parseTree(headId));
				treeWalk.setRecursive(true);

				while (treeWalk.next()) {
					String path = treeWalk.getPathString();
					CanonicalTreeParser hTree = null;
					if (hIdx != -1)
						hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class);
					if (!ignorePaths.contains(path)) {
						// add entries from HEAD for all other paths
						if (hTree != null) {
							// create a new DirCacheEntry with data retrieved
							// from
							// HEAD
							final DirCacheEntry dcEntry = new DirCacheEntry(path);
							dcEntry.setObjectId(hTree.getEntryObjectId());
							dcEntry.setFileMode(hTree.getEntryFileMode());

							// add to temporary in-core index
							dcBuilder.add(dcEntry);
						}
					}
				}

				// release the treewalk
				treeWalk.release();
			}
			
			// finish temporary in-core index used for this commit
			dcBuilder.finish();
		} finally {
			inserter.release();
		}
		return inCoreIndex;
	}

	private static List<File> listFiles(File folder) {
		List<File> files = new ArrayList<File>();
		for (File file : folder.listFiles()) {
			if (file.isDirectory()) {
				files.addAll(listFiles(file));
			} else {
				files.add(file);
			}
		}
		return files;
	}

	/**
	 * JCommander Parameters class for BuildGhPages.
	 */
	@Parameters(separators = " ")
	private static class Params {

		@Parameter(names = { "--sourceFolder" }, description = "Source folder for pages", required = true)
		public String sourceFolder;

		@Parameter(names = { "--repository" }, description = "Repository folder", required = true)
		public String repositoryFolder;

		@Parameter(names = { "--obliterate" }, description = "Replace gh-pages tree with only the content in your sourcefolder")
		public boolean obliterate;

	}
}