commit | author | age
|
f8bb95
|
1 |
/* |
JM |
2 |
* Copyright 2013 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.utils; |
|
17 |
|
|
18 |
import java.io.IOException; |
cf17b2
|
19 |
import java.text.DateFormat; |
f8bb95
|
20 |
import java.text.MessageFormat; |
cf17b2
|
21 |
import java.text.SimpleDateFormat; |
f8bb95
|
22 |
import java.util.ArrayList; |
271a68
|
23 |
import java.util.Arrays; |
f8bb95
|
24 |
import java.util.Collection; |
JM |
25 |
import java.util.Collections; |
|
26 |
import java.util.Date; |
0eb562
|
27 |
import java.util.HashMap; |
f8bb95
|
28 |
import java.util.List; |
0eb562
|
29 |
import java.util.Map; |
f8bb95
|
30 |
import java.util.Set; |
9b26b7
|
31 |
import java.util.TimeZone; |
f8bb95
|
32 |
import java.util.TreeSet; |
JM |
33 |
|
|
34 |
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; |
|
35 |
import org.eclipse.jgit.api.errors.JGitInternalException; |
|
36 |
import org.eclipse.jgit.dircache.DirCache; |
|
37 |
import org.eclipse.jgit.dircache.DirCacheBuilder; |
|
38 |
import org.eclipse.jgit.dircache.DirCacheEntry; |
|
39 |
import org.eclipse.jgit.internal.JGitText; |
|
40 |
import org.eclipse.jgit.lib.CommitBuilder; |
|
41 |
import org.eclipse.jgit.lib.FileMode; |
|
42 |
import org.eclipse.jgit.lib.ObjectId; |
|
43 |
import org.eclipse.jgit.lib.ObjectInserter; |
|
44 |
import org.eclipse.jgit.lib.PersonIdent; |
271a68
|
45 |
import org.eclipse.jgit.lib.Ref; |
ff7d3c
|
46 |
import org.eclipse.jgit.lib.RefRename; |
f8bb95
|
47 |
import org.eclipse.jgit.lib.RefUpdate; |
JM |
48 |
import org.eclipse.jgit.lib.RefUpdate.Result; |
|
49 |
import org.eclipse.jgit.lib.Repository; |
|
50 |
import org.eclipse.jgit.revwalk.RevCommit; |
|
51 |
import org.eclipse.jgit.revwalk.RevWalk; |
|
52 |
import org.eclipse.jgit.transport.ReceiveCommand; |
|
53 |
import org.eclipse.jgit.treewalk.CanonicalTreeParser; |
|
54 |
import org.eclipse.jgit.treewalk.TreeWalk; |
|
55 |
import org.slf4j.Logger; |
|
56 |
import org.slf4j.LoggerFactory; |
|
57 |
|
9b26b7
|
58 |
import com.gitblit.Constants; |
cf17b2
|
59 |
import com.gitblit.models.DailyLogEntry; |
f8bb95
|
60 |
import com.gitblit.models.PathModel.PathChangeModel; |
ff7d3c
|
61 |
import com.gitblit.models.RefLogEntry; |
f8bb95
|
62 |
import com.gitblit.models.RefModel; |
0eb562
|
63 |
import com.gitblit.models.RepositoryCommit; |
f8bb95
|
64 |
import com.gitblit.models.UserModel; |
JM |
65 |
|
|
66 |
/** |
ff7d3c
|
67 |
* Utility class for maintaining a reflog within a git repository on an |
f8bb95
|
68 |
* orphan branch. |
699e71
|
69 |
* |
f8bb95
|
70 |
* @author James Moger |
JM |
71 |
* |
|
72 |
*/ |
ff7d3c
|
73 |
public class RefLogUtils { |
699e71
|
74 |
|
c134a0
|
75 |
private static final String GB_REFLOG = "refs/meta/gitblit/reflog"; |
f8bb95
|
76 |
|
ff7d3c
|
77 |
private static final Logger LOGGER = LoggerFactory.getLogger(RefLogUtils.class); |
f8bb95
|
78 |
|
JM |
79 |
/** |
|
80 |
* Log an error message and exception. |
699e71
|
81 |
* |
f8bb95
|
82 |
* @param t |
JM |
83 |
* @param repository |
|
84 |
* if repository is not null it MUST be the {0} parameter in the |
|
85 |
* pattern. |
|
86 |
* @param pattern |
|
87 |
* @param objects |
|
88 |
*/ |
|
89 |
private static void error(Throwable t, Repository repository, String pattern, Object... objects) { |
|
90 |
List<Object> parameters = new ArrayList<Object>(); |
|
91 |
if (objects != null && objects.length > 0) { |
|
92 |
for (Object o : objects) { |
|
93 |
parameters.add(o); |
|
94 |
} |
|
95 |
} |
|
96 |
if (repository != null) { |
|
97 |
parameters.add(0, repository.getDirectory().getAbsolutePath()); |
|
98 |
} |
|
99 |
LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t); |
271a68
|
100 |
} |
699e71
|
101 |
|
271a68
|
102 |
/** |
JM |
103 |
* Returns true if the repository has a reflog branch. |
699e71
|
104 |
* |
271a68
|
105 |
* @param repository |
JM |
106 |
* @return true if the repository has a reflog branch |
|
107 |
*/ |
|
108 |
public static boolean hasRefLogBranch(Repository repository) { |
|
109 |
try { |
|
110 |
return repository.getRef(GB_REFLOG) != null; |
|
111 |
} catch(Exception e) { |
|
112 |
LOGGER.error("failed to determine hasRefLogBranch", e); |
|
113 |
} |
|
114 |
return false; |
f8bb95
|
115 |
} |
JM |
116 |
|
|
117 |
/** |
ff7d3c
|
118 |
* Returns a RefModel for the reflog branch in the repository. If the |
f8bb95
|
119 |
* branch can not be found, null is returned. |
699e71
|
120 |
* |
f8bb95
|
121 |
* @param repository |
ff7d3c
|
122 |
* @return a refmodel for the reflog branch or null |
f8bb95
|
123 |
*/ |
ff7d3c
|
124 |
public static RefModel getRefLogBranch(Repository repository) { |
c134a0
|
125 |
List<RefModel> refs = JGitUtils.getRefs(repository, "refs/"); |
JM |
126 |
Ref oldRef = null; |
f8bb95
|
127 |
for (RefModel ref : refs) { |
ff7d3c
|
128 |
if (ref.reference.getName().equals(GB_REFLOG)) { |
f8bb95
|
129 |
return ref; |
c134a0
|
130 |
} else if (ref.reference.getName().equals("refs/gitblit/reflog")) { |
JM |
131 |
oldRef = ref.reference; |
|
132 |
} else if (ref.reference.getName().equals("refs/gitblit/pushes")) { |
|
133 |
oldRef = ref.reference; |
ff7d3c
|
134 |
} |
JM |
135 |
} |
c134a0
|
136 |
if (oldRef != null) { |
JM |
137 |
// rename old ref to refs/meta/gitblit/reflog |
ff7d3c
|
138 |
RefRename cmd; |
JM |
139 |
try { |
c134a0
|
140 |
cmd = repository.renameRef(oldRef.getName(), GB_REFLOG); |
ff7d3c
|
141 |
cmd.setRefLogIdent(new PersonIdent("Gitblit", "gitblit@localhost")); |
c134a0
|
142 |
cmd.setRefLogMessage("renamed " + oldRef.getName() + " => " + GB_REFLOG); |
ff7d3c
|
143 |
Result res = cmd.rename(); |
JM |
144 |
switch (res) { |
|
145 |
case RENAMED: |
c134a0
|
146 |
LOGGER.info(repository.getDirectory() + " " + cmd.getRefLogMessage()); |
ff7d3c
|
147 |
return getRefLogBranch(repository); |
JM |
148 |
default: |
c134a0
|
149 |
LOGGER.error("failed to rename " + oldRef.getName() + " => " + GB_REFLOG + " (" + res.name() + ")"); |
ff7d3c
|
150 |
} |
JM |
151 |
} catch (IOException e) { |
c134a0
|
152 |
LOGGER.error("failed to rename reflog", e); |
f8bb95
|
153 |
} |
JM |
154 |
} |
|
155 |
return null; |
|
156 |
} |
699e71
|
157 |
|
cf17b2
|
158 |
private static UserModel newUserModelFrom(PersonIdent ident) { |
JM |
159 |
String name = ident.getName(); |
|
160 |
String username; |
|
161 |
String displayname; |
|
162 |
if (name.indexOf('/') > -1) { |
|
163 |
int slash = name.indexOf('/'); |
|
164 |
displayname = name.substring(0, slash); |
|
165 |
username = name.substring(slash + 1); |
|
166 |
} else { |
|
167 |
displayname = name; |
|
168 |
username = ident.getEmailAddress(); |
|
169 |
} |
699e71
|
170 |
|
cf17b2
|
171 |
UserModel user = new UserModel(username); |
JM |
172 |
user.displayName = displayname; |
|
173 |
user.emailAddress = ident.getEmailAddress(); |
|
174 |
return user; |
|
175 |
} |
699e71
|
176 |
|
f8bb95
|
177 |
/** |
271a68
|
178 |
* Logs a ref deletion. |
699e71
|
179 |
* |
271a68
|
180 |
* @param user |
JM |
181 |
* @param repository |
|
182 |
* @param ref |
|
183 |
* @return true, if the update was successful |
|
184 |
*/ |
88ec32
|
185 |
public static boolean deleteRef(UserModel user, Repository repository, Ref ref) { |
271a68
|
186 |
try { |
88ec32
|
187 |
if (ref == null) { |
JM |
188 |
return false; |
271a68
|
189 |
} |
88ec32
|
190 |
RefModel reflogBranch = getRefLogBranch(repository); |
JM |
191 |
if (reflogBranch == null) { |
271a68
|
192 |
return false; |
JM |
193 |
} |
699e71
|
194 |
|
88ec32
|
195 |
List<RevCommit> log = JGitUtils.getRevLog(repository, reflogBranch.getName(), ref.getName(), 0, 1); |
JM |
196 |
if (log.isEmpty()) { |
|
197 |
// this ref is not in the reflog branch |
|
198 |
return false; |
|
199 |
} |
|
200 |
ReceiveCommand cmd = new ReceiveCommand(ref.getObjectId(), ObjectId.zeroId(), ref.getName()); |
271a68
|
201 |
return updateRefLog(user, repository, Arrays.asList(cmd)); |
JM |
202 |
} catch (Throwable t) { |
|
203 |
error(t, repository, "Failed to commit reflog entry to {0}"); |
|
204 |
} |
|
205 |
return false; |
|
206 |
} |
699e71
|
207 |
|
271a68
|
208 |
/** |
ff7d3c
|
209 |
* Updates the reflog with the received commands. |
699e71
|
210 |
* |
f8bb95
|
211 |
* @param user |
JM |
212 |
* @param repository |
|
213 |
* @param commands |
|
214 |
* @return true, if the update was successful |
|
215 |
*/ |
ff7d3c
|
216 |
public static boolean updateRefLog(UserModel user, Repository repository, |
f8bb95
|
217 |
Collection<ReceiveCommand> commands) { |
5e3521
|
218 |
|
JM |
219 |
// only track branches and tags |
|
220 |
List<ReceiveCommand> filteredCommands = new ArrayList<ReceiveCommand>(); |
|
221 |
for (ReceiveCommand cmd : commands) { |
|
222 |
if (!cmd.getRefName().startsWith(Constants.R_HEADS) |
|
223 |
&& !cmd.getRefName().startsWith(Constants.R_TAGS)) { |
|
224 |
continue; |
|
225 |
} |
|
226 |
filteredCommands.add(cmd); |
|
227 |
} |
|
228 |
|
|
229 |
if (filteredCommands.isEmpty()) { |
|
230 |
// nothing to log |
|
231 |
return true; |
|
232 |
} |
|
233 |
|
ff7d3c
|
234 |
RefModel reflogBranch = getRefLogBranch(repository); |
JM |
235 |
if (reflogBranch == null) { |
|
236 |
JGitUtils.createOrphanBranch(repository, GB_REFLOG, null); |
f8bb95
|
237 |
} |
699e71
|
238 |
|
f8bb95
|
239 |
boolean success = false; |
JM |
240 |
String message = "push"; |
699e71
|
241 |
|
f8bb95
|
242 |
try { |
ff7d3c
|
243 |
ObjectId headId = repository.resolve(GB_REFLOG + "^{commit}"); |
f8bb95
|
244 |
ObjectInserter odi = repository.newObjectInserter(); |
JM |
245 |
try { |
c134a0
|
246 |
// Create the in-memory index of the reflog log entry |
f8bb95
|
247 |
DirCache index = createIndex(repository, headId, commands); |
JM |
248 |
ObjectId indexTreeId = index.writeTree(odi); |
|
249 |
|
cf17b2
|
250 |
PersonIdent ident; |
JM |
251 |
if (UserModel.ANONYMOUS.equals(user)) { |
|
252 |
// anonymous push |
b5798e
|
253 |
ident = new PersonIdent(user.username + "/" + user.username, user.username); |
cf17b2
|
254 |
} else { |
JM |
255 |
// construct real pushing account |
|
256 |
ident = new PersonIdent(MessageFormat.format("{0}/{1}", user.getDisplayName(), user.username), |
5030bb
|
257 |
user.emailAddress == null ? user.username : user.emailAddress); |
cf17b2
|
258 |
} |
f8bb95
|
259 |
|
JM |
260 |
// Create a commit object |
|
261 |
CommitBuilder commit = new CommitBuilder(); |
|
262 |
commit.setAuthor(ident); |
|
263 |
commit.setCommitter(ident); |
9b26b7
|
264 |
commit.setEncoding(Constants.ENCODING); |
f8bb95
|
265 |
commit.setMessage(message); |
JM |
266 |
commit.setParentId(headId); |
|
267 |
commit.setTreeId(indexTreeId); |
|
268 |
|
|
269 |
// Insert the commit into the repository |
|
270 |
ObjectId commitId = odi.insert(commit); |
|
271 |
odi.flush(); |
|
272 |
|
|
273 |
RevWalk revWalk = new RevWalk(repository); |
|
274 |
try { |
|
275 |
RevCommit revCommit = revWalk.parseCommit(commitId); |
ff7d3c
|
276 |
RefUpdate ru = repository.updateRef(GB_REFLOG); |
f8bb95
|
277 |
ru.setNewObjectId(commitId); |
JM |
278 |
ru.setExpectedOldObjectId(headId); |
|
279 |
ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false); |
|
280 |
Result rc = ru.forceUpdate(); |
|
281 |
switch (rc) { |
|
282 |
case NEW: |
|
283 |
case FORCED: |
|
284 |
case FAST_FORWARD: |
|
285 |
success = true; |
|
286 |
break; |
|
287 |
case REJECTED: |
|
288 |
case LOCK_FAILURE: |
|
289 |
throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD, |
|
290 |
ru.getRef(), rc); |
|
291 |
default: |
|
292 |
throw new JGitInternalException(MessageFormat.format( |
ff7d3c
|
293 |
JGitText.get().updatingRefFailed, GB_REFLOG, commitId.toString(), |
f8bb95
|
294 |
rc)); |
JM |
295 |
} |
|
296 |
} finally { |
a1cee6
|
297 |
revWalk.close(); |
f8bb95
|
298 |
} |
JM |
299 |
} finally { |
a1cee6
|
300 |
odi.close(); |
f8bb95
|
301 |
} |
JM |
302 |
} catch (Throwable t) { |
ff7d3c
|
303 |
error(t, repository, "Failed to commit reflog entry to {0}"); |
f8bb95
|
304 |
} |
JM |
305 |
return success; |
|
306 |
} |
699e71
|
307 |
|
f8bb95
|
308 |
/** |
c134a0
|
309 |
* Creates an in-memory index of the reflog entry. |
699e71
|
310 |
* |
f8bb95
|
311 |
* @param repo |
JM |
312 |
* @param headId |
|
313 |
* @param commands |
|
314 |
* @return an in-memory index |
|
315 |
* @throws IOException |
|
316 |
*/ |
699e71
|
317 |
private static DirCache createIndex(Repository repo, ObjectId headId, |
f8bb95
|
318 |
Collection<ReceiveCommand> commands) throws IOException { |
JM |
319 |
|
|
320 |
DirCache inCoreIndex = DirCache.newInCore(); |
|
321 |
DirCacheBuilder dcBuilder = inCoreIndex.builder(); |
|
322 |
ObjectInserter inserter = repo.newObjectInserter(); |
|
323 |
|
|
324 |
long now = System.currentTimeMillis(); |
|
325 |
Set<String> ignorePaths = new TreeSet<String>(); |
|
326 |
try { |
|
327 |
// add receive commands to the temporary index |
|
328 |
for (ReceiveCommand command : commands) { |
|
329 |
// use the ref names as the path names |
|
330 |
String path = command.getRefName(); |
|
331 |
ignorePaths.add(path); |
|
332 |
|
|
333 |
StringBuilder change = new StringBuilder(); |
|
334 |
change.append(command.getType().name()).append(' '); |
|
335 |
switch (command.getType()) { |
|
336 |
case CREATE: |
|
337 |
change.append(ObjectId.zeroId().getName()); |
|
338 |
change.append(' '); |
|
339 |
change.append(command.getNewId().getName()); |
|
340 |
break; |
|
341 |
case UPDATE: |
|
342 |
case UPDATE_NONFASTFORWARD: |
|
343 |
change.append(command.getOldId().getName()); |
|
344 |
change.append(' '); |
|
345 |
change.append(command.getNewId().getName()); |
|
346 |
break; |
|
347 |
case DELETE: |
|
348 |
change = null; |
|
349 |
break; |
|
350 |
} |
|
351 |
if (change == null) { |
|
352 |
// ref deleted |
|
353 |
continue; |
|
354 |
} |
|
355 |
String content = change.toString(); |
699e71
|
356 |
|
f8bb95
|
357 |
// create an index entry for this attachment |
JM |
358 |
final DirCacheEntry dcEntry = new DirCacheEntry(path); |
|
359 |
dcEntry.setLength(content.length()); |
|
360 |
dcEntry.setLastModified(now); |
|
361 |
dcEntry.setFileMode(FileMode.REGULAR_FILE); |
|
362 |
|
|
363 |
// insert object |
9b26b7
|
364 |
dcEntry.setObjectId(inserter.insert(org.eclipse.jgit.lib.Constants.OBJ_BLOB, content.getBytes("UTF-8"))); |
f8bb95
|
365 |
|
JM |
366 |
// add to temporary in-core index |
|
367 |
dcBuilder.add(dcEntry); |
|
368 |
} |
|
369 |
|
|
370 |
// Traverse HEAD to add all other paths |
|
371 |
TreeWalk treeWalk = new TreeWalk(repo); |
|
372 |
int hIdx = -1; |
|
373 |
if (headId != null) |
|
374 |
hIdx = treeWalk.addTree(new RevWalk(repo).parseTree(headId)); |
|
375 |
treeWalk.setRecursive(true); |
|
376 |
|
|
377 |
while (treeWalk.next()) { |
|
378 |
String path = treeWalk.getPathString(); |
|
379 |
CanonicalTreeParser hTree = null; |
|
380 |
if (hIdx != -1) |
|
381 |
hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class); |
|
382 |
if (!ignorePaths.contains(path)) { |
|
383 |
// add entries from HEAD for all other paths |
|
384 |
if (hTree != null) { |
|
385 |
// create a new DirCacheEntry with data retrieved from |
|
386 |
// HEAD |
|
387 |
final DirCacheEntry dcEntry = new DirCacheEntry(path); |
|
388 |
dcEntry.setObjectId(hTree.getEntryObjectId()); |
|
389 |
dcEntry.setFileMode(hTree.getEntryFileMode()); |
|
390 |
|
|
391 |
// add to temporary in-core index |
|
392 |
dcBuilder.add(dcEntry); |
|
393 |
} |
|
394 |
} |
|
395 |
} |
|
396 |
|
|
397 |
// release the treewalk |
a1cee6
|
398 |
treeWalk.close(); |
f8bb95
|
399 |
|
JM |
400 |
// finish temporary in-core index used for this commit |
|
401 |
dcBuilder.finish(); |
|
402 |
} finally { |
a1cee6
|
403 |
inserter.close(); |
f8bb95
|
404 |
} |
JM |
405 |
return inCoreIndex; |
|
406 |
} |
699e71
|
407 |
|
ff7d3c
|
408 |
public static List<RefLogEntry> getRefLog(String repositoryName, Repository repository) { |
JM |
409 |
return getRefLog(repositoryName, repository, null, 0, -1); |
f8bb95
|
410 |
} |
JM |
411 |
|
ff7d3c
|
412 |
public static List<RefLogEntry> getRefLog(String repositoryName, Repository repository, int maxCount) { |
JM |
413 |
return getRefLog(repositoryName, repository, null, 0, maxCount); |
0eb562
|
414 |
} |
JM |
415 |
|
ff7d3c
|
416 |
public static List<RefLogEntry> getRefLog(String repositoryName, Repository repository, int offset, int maxCount) { |
JM |
417 |
return getRefLog(repositoryName, repository, null, offset, maxCount); |
f8bb95
|
418 |
} |
JM |
419 |
|
ff7d3c
|
420 |
public static List<RefLogEntry> getRefLog(String repositoryName, Repository repository, Date minimumDate) { |
JM |
421 |
return getRefLog(repositoryName, repository, minimumDate, 0, -1); |
f8bb95
|
422 |
} |
699e71
|
423 |
|
0eb562
|
424 |
/** |
ff7d3c
|
425 |
* Returns the list of reflog entries as they were recorded by Gitblit. |
JM |
426 |
* Each RefLogEntry may represent multiple ref updates. |
699e71
|
427 |
* |
0eb562
|
428 |
* @param repositoryName |
JM |
429 |
* @param repository |
|
430 |
* @param minimumDate |
|
431 |
* @param offset |
|
432 |
* @param maxCount |
c134a0
|
433 |
* if < 0, all entries are returned. |
JM |
434 |
* @return a list of reflog entries |
0eb562
|
435 |
*/ |
ff7d3c
|
436 |
public static List<RefLogEntry> getRefLog(String repositoryName, Repository repository, |
0eb562
|
437 |
Date minimumDate, int offset, int maxCount) { |
ff7d3c
|
438 |
List<RefLogEntry> list = new ArrayList<RefLogEntry>(); |
JM |
439 |
RefModel ref = getRefLogBranch(repository); |
f8bb95
|
440 |
if (ref == null) { |
JM |
441 |
return list; |
|
442 |
} |
0eb562
|
443 |
if (maxCount == 0) { |
JM |
444 |
return list; |
|
445 |
} |
699e71
|
446 |
|
0eb562
|
447 |
Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository); |
f8bb95
|
448 |
List<RevCommit> pushes; |
JM |
449 |
if (minimumDate == null) { |
ff7d3c
|
450 |
pushes = JGitUtils.getRevLog(repository, GB_REFLOG, offset, maxCount); |
f8bb95
|
451 |
} else { |
ff7d3c
|
452 |
pushes = JGitUtils.getRevLog(repository, GB_REFLOG, minimumDate); |
f8bb95
|
453 |
} |
JM |
454 |
for (RevCommit push : pushes) { |
|
455 |
if (push.getAuthorIdent().getName().equalsIgnoreCase("gitblit")) { |
|
456 |
// skip gitblit/internal commits |
|
457 |
continue; |
|
458 |
} |
5030bb
|
459 |
|
cf17b2
|
460 |
UserModel user = newUserModelFrom(push.getAuthorIdent()); |
f8bb95
|
461 |
Date date = push.getAuthorIdent().getWhen(); |
699e71
|
462 |
|
JM |
463 |
RefLogEntry log = new RefLogEntry(repositoryName, date, user); |
5e3521
|
464 |
|
JM |
465 |
// only report HEADS and TAGS for now |
|
466 |
List<PathChangeModel> changedRefs = new ArrayList<PathChangeModel>(); |
|
467 |
for (PathChangeModel refChange : JGitUtils.getFilesInCommit(repository, push)) { |
|
468 |
if (refChange.path.startsWith(Constants.R_HEADS) |
|
469 |
|| refChange.path.startsWith(Constants.R_TAGS)) { |
|
470 |
changedRefs.add(refChange); |
|
471 |
} |
|
472 |
} |
88ec32
|
473 |
if (changedRefs.isEmpty()) { |
JM |
474 |
// skip empty commits |
|
475 |
continue; |
|
476 |
} |
|
477 |
list.add(log); |
f8bb95
|
478 |
for (PathChangeModel change : changedRefs) { |
JM |
479 |
switch (change.changeType) { |
|
480 |
case DELETE: |
402730
|
481 |
log.updateRef(change.path, ReceiveCommand.Type.DELETE); |
f8bb95
|
482 |
break; |
402730
|
483 |
default: |
f8bb95
|
484 |
String content = JGitUtils.getStringContent(repository, push.getTree(), change.path); |
JM |
485 |
String [] fields = content.split(" "); |
|
486 |
String oldId = fields[1]; |
|
487 |
String newId = fields[2]; |
0eb562
|
488 |
log.updateRef(change.path, ReceiveCommand.Type.valueOf(fields[0]), oldId, newId); |
88ec32
|
489 |
if (ObjectId.zeroId().getName().equals(newId)) { |
JM |
490 |
// ref deletion |
|
491 |
continue; |
|
492 |
} |
5e3521
|
493 |
try { |
JM |
494 |
List<RevCommit> pushedCommits = JGitUtils.getRevLog(repository, oldId, newId); |
|
495 |
for (RevCommit pushedCommit : pushedCommits) { |
|
496 |
RepositoryCommit repoCommit = log.addCommit(change.path, pushedCommit); |
|
497 |
if (repoCommit != null) { |
|
498 |
repoCommit.setRefs(allRefs.get(pushedCommit.getId())); |
|
499 |
} |
0eb562
|
500 |
} |
5e3521
|
501 |
} catch (Exception e) { |
JM |
502 |
|
f8bb95
|
503 |
} |
JM |
504 |
} |
|
505 |
} |
|
506 |
} |
|
507 |
Collections.sort(list); |
|
508 |
return list; |
|
509 |
} |
0eb562
|
510 |
|
JM |
511 |
/** |
c134a0
|
512 |
* Returns the list of entries organized by ref (e.g. each ref has it's own |
JM |
513 |
* RefLogEntry object). |
699e71
|
514 |
* |
0eb562
|
515 |
* @param repositoryName |
JM |
516 |
* @param repository |
|
517 |
* @param maxCount |
c134a0
|
518 |
* @return a list of reflog entries separated by ref |
0eb562
|
519 |
*/ |
ff7d3c
|
520 |
public static List<RefLogEntry> getLogByRef(String repositoryName, Repository repository, int maxCount) { |
JM |
521 |
return getLogByRef(repositoryName, repository, 0, maxCount); |
0eb562
|
522 |
} |
699e71
|
523 |
|
0eb562
|
524 |
/** |
c134a0
|
525 |
* Returns the list of entries organized by ref (e.g. each ref has it's own |
JM |
526 |
* RefLogEntry object). |
699e71
|
527 |
* |
0eb562
|
528 |
* @param repositoryName |
JM |
529 |
* @param repository |
|
530 |
* @param offset |
|
531 |
* @param maxCount |
c134a0
|
532 |
* @return a list of reflog entries separated by ref |
0eb562
|
533 |
*/ |
ff7d3c
|
534 |
public static List<RefLogEntry> getLogByRef(String repositoryName, Repository repository, int offset, |
0eb562
|
535 |
int maxCount) { |
c134a0
|
536 |
// break the reflog into ref entries and then merge them back into a list |
ff7d3c
|
537 |
Map<String, List<RefLogEntry>> refMap = new HashMap<String, List<RefLogEntry>>(); |
JM |
538 |
List<RefLogEntry> refLog = getRefLog(repositoryName, repository, offset, maxCount); |
|
539 |
for (RefLogEntry entry : refLog) { |
|
540 |
for (String ref : entry.getChangedRefs()) { |
0eb562
|
541 |
if (!refMap.containsKey(ref)) { |
ff7d3c
|
542 |
refMap.put(ref, new ArrayList<RefLogEntry>()); |
0eb562
|
543 |
} |
699e71
|
544 |
|
ff7d3c
|
545 |
// construct new ref-specific ref change entry |
JM |
546 |
RefLogEntry refChange; |
|
547 |
if (entry instanceof DailyLogEntry) { |
c134a0
|
548 |
// simulated reflog from commits grouped by date |
ff7d3c
|
549 |
refChange = new DailyLogEntry(entry.repository, entry.date); |
cf17b2
|
550 |
} else { |
c134a0
|
551 |
// real reflog entry |
ff7d3c
|
552 |
refChange = new RefLogEntry(entry.repository, entry.date, entry.user); |
cf17b2
|
553 |
} |
ff7d3c
|
554 |
refChange.updateRef(ref, entry.getChangeType(ref), entry.getOldId(ref), entry.getNewId(ref)); |
JM |
555 |
refChange.addCommits(entry.getCommits(ref)); |
|
556 |
refMap.get(ref).add(refChange); |
0eb562
|
557 |
} |
JM |
558 |
} |
699e71
|
559 |
|
ff7d3c
|
560 |
// merge individual ref changes into master list |
JM |
561 |
List<RefLogEntry> mergedRefLog = new ArrayList<RefLogEntry>(); |
|
562 |
for (List<RefLogEntry> refPush : refMap.values()) { |
|
563 |
mergedRefLog.addAll(refPush); |
0eb562
|
564 |
} |
699e71
|
565 |
|
ff7d3c
|
566 |
// sort ref log |
JM |
567 |
Collections.sort(mergedRefLog); |
699e71
|
568 |
|
ff7d3c
|
569 |
return mergedRefLog; |
0eb562
|
570 |
} |
699e71
|
571 |
|
5c1588
|
572 |
/** |
ff7d3c
|
573 |
* Returns the list of ref changes separated by ref (e.g. each ref has it's own |
JM |
574 |
* RefLogEntry object). |
699e71
|
575 |
* |
5c1588
|
576 |
* @param repositoryName |
JM |
577 |
* @param repository |
|
578 |
* @param minimumDate |
ff7d3c
|
579 |
* @return a list of ref log entries separated by ref |
5c1588
|
580 |
*/ |
ff7d3c
|
581 |
public static List<RefLogEntry> getLogByRef(String repositoryName, Repository repository, Date minimumDate) { |
c134a0
|
582 |
// break the reflog into refs and then merge them back into a list |
ff7d3c
|
583 |
Map<String, List<RefLogEntry>> refMap = new HashMap<String, List<RefLogEntry>>(); |
c134a0
|
584 |
List<RefLogEntry> entries = getRefLog(repositoryName, repository, minimumDate); |
JM |
585 |
for (RefLogEntry entry : entries) { |
|
586 |
for (String ref : entry.getChangedRefs()) { |
5c1588
|
587 |
if (!refMap.containsKey(ref)) { |
ff7d3c
|
588 |
refMap.put(ref, new ArrayList<RefLogEntry>()); |
5c1588
|
589 |
} |
cf17b2
|
590 |
|
c134a0
|
591 |
// construct new ref-specific log entry |
JM |
592 |
RefLogEntry refPush = new RefLogEntry(entry.repository, entry.date, entry.user); |
|
593 |
refPush.updateRef(ref, entry.getChangeType(ref), entry.getOldId(ref), entry.getNewId(ref)); |
|
594 |
refPush.addCommits(entry.getCommits(ref)); |
5c1588
|
595 |
refMap.get(ref).add(refPush); |
JM |
596 |
} |
|
597 |
} |
699e71
|
598 |
|
c134a0
|
599 |
// merge individual ref entries into master list |
JM |
600 |
List<RefLogEntry> refLog = new ArrayList<RefLogEntry>(); |
|
601 |
for (List<RefLogEntry> entry : refMap.values()) { |
|
602 |
refLog.addAll(entry); |
5c1588
|
603 |
} |
699e71
|
604 |
|
c134a0
|
605 |
Collections.sort(refLog); |
699e71
|
606 |
|
c134a0
|
607 |
return refLog; |
5c1588
|
608 |
} |
cf17b2
|
609 |
|
JM |
610 |
/** |
|
611 |
* Returns a commit log grouped by day. |
|
612 |
* |
|
613 |
* @param repositoryName |
|
614 |
* @param repository |
|
615 |
* @param minimumDate |
|
616 |
* @param offset |
|
617 |
* @param maxCount |
c134a0
|
618 |
* if < 0, all entries are returned. |
9b26b7
|
619 |
* @param the timezone to use when aggregating commits by date |
cf17b2
|
620 |
* @return a list of grouped commit log entries |
JM |
621 |
*/ |
|
622 |
public static List<DailyLogEntry> getDailyLog(String repositoryName, Repository repository, |
9b26b7
|
623 |
Date minimumDate, int offset, int maxCount, |
JM |
624 |
TimeZone timezone) { |
cf17b2
|
625 |
DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); |
9b26b7
|
626 |
df.setTimeZone(timezone); |
cf17b2
|
627 |
|
JM |
628 |
Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository); |
|
629 |
Map<String, DailyLogEntry> tags = new HashMap<String, DailyLogEntry>(); |
9b26b7
|
630 |
Map<String, DailyLogEntry> pulls = new HashMap<String, DailyLogEntry>(); |
cf17b2
|
631 |
Map<String, DailyLogEntry> dailydigests = new HashMap<String, DailyLogEntry>(); |
9b26b7
|
632 |
String linearParent = null; |
cf17b2
|
633 |
for (RefModel local : JGitUtils.getLocalBranches(repository, true, -1)) { |
3c4ce1
|
634 |
if (!local.getDate().after(minimumDate)) { |
JM |
635 |
// branch not recently updated |
|
636 |
continue; |
|
637 |
} |
cf17b2
|
638 |
String branch = local.getName(); |
79dd0b
|
639 |
List<RepositoryCommit> commits = CommitCache.instance().getCommits(repositoryName, repository, branch, minimumDate); |
5505fe
|
640 |
linearParent = null; |
79dd0b
|
641 |
for (RepositoryCommit commit : commits) { |
9b26b7
|
642 |
if (linearParent != null) { |
JM |
643 |
if (!commit.getName().equals(linearParent)) { |
|
644 |
// only follow linear branch commits |
|
645 |
continue; |
|
646 |
} |
|
647 |
} |
79dd0b
|
648 |
Date date = commit.getCommitDate(); |
cf17b2
|
649 |
String dateStr = df.format(date); |
JM |
650 |
if (!dailydigests.containsKey(dateStr)) { |
|
651 |
dailydigests.put(dateStr, new DailyLogEntry(repositoryName, date)); |
|
652 |
} |
9b26b7
|
653 |
DailyLogEntry digest = dailydigests.get(dateStr); |
b5a318
|
654 |
if (commit.getParentCount() == 0) { |
9b26b7
|
655 |
linearParent = null; |
b5a318
|
656 |
digest.updateRef(branch, ReceiveCommand.Type.CREATE); |
JM |
657 |
} else { |
9b26b7
|
658 |
linearParent = commit.getParents()[0].getId().getName(); |
JM |
659 |
digest.updateRef(branch, ReceiveCommand.Type.UPDATE, linearParent, commit.getName()); |
b5a318
|
660 |
} |
699e71
|
661 |
|
79dd0b
|
662 |
RepositoryCommit repoCommit = digest.addCommit(commit); |
cf17b2
|
663 |
if (repoCommit != null) { |
9b26b7
|
664 |
List<RefModel> matchedRefs = allRefs.get(commit.getId()); |
JM |
665 |
repoCommit.setRefs(matchedRefs); |
699e71
|
666 |
|
9b26b7
|
667 |
if (!ArrayUtils.isEmpty(matchedRefs)) { |
JM |
668 |
for (RefModel ref : matchedRefs) { |
cf17b2
|
669 |
if (ref.getName().startsWith(Constants.R_TAGS)) { |
9b26b7
|
670 |
// treat tags as special events in the log |
cf17b2
|
671 |
if (!tags.containsKey(dateStr)) { |
5131d6
|
672 |
UserModel tagUser = newUserModelFrom(ref.getAuthorIdent()); |
cf17b2
|
673 |
Date tagDate = commit.getAuthorIdent().getWhen(); |
9b26b7
|
674 |
tags.put(dateStr, new DailyLogEntry(repositoryName, tagDate, tagUser)); |
cf17b2
|
675 |
} |
ff7d3c
|
676 |
RefLogEntry tagEntry = tags.get(dateStr); |
cf17b2
|
677 |
tagEntry.updateRef(ref.getName(), ReceiveCommand.Type.CREATE); |
79dd0b
|
678 |
RepositoryCommit rc = repoCommit.clone(ref.getName()); |
JM |
679 |
tagEntry.addCommit(rc); |
9b26b7
|
680 |
} else if (ref.getName().startsWith(Constants.R_PULL)) { |
JM |
681 |
// treat pull requests as special events in the log |
|
682 |
if (!pulls.containsKey(dateStr)) { |
5131d6
|
683 |
UserModel commitUser = newUserModelFrom(ref.getAuthorIdent()); |
9b26b7
|
684 |
Date commitDate = commit.getAuthorIdent().getWhen(); |
JM |
685 |
pulls.put(dateStr, new DailyLogEntry(repositoryName, commitDate, commitUser)); |
|
686 |
} |
ff7d3c
|
687 |
RefLogEntry pullEntry = pulls.get(dateStr); |
9b26b7
|
688 |
pullEntry.updateRef(ref.getName(), ReceiveCommand.Type.CREATE); |
79dd0b
|
689 |
RepositoryCommit rc = repoCommit.clone(ref.getName()); |
JM |
690 |
pullEntry.addCommit(rc); |
cf17b2
|
691 |
} |
JM |
692 |
} |
|
693 |
} |
|
694 |
} |
|
695 |
} |
|
696 |
} |
|
697 |
|
|
698 |
List<DailyLogEntry> list = new ArrayList<DailyLogEntry>(dailydigests.values()); |
|
699 |
list.addAll(tags.values()); |
9b26b7
|
700 |
//list.addAll(pulls.values()); |
cf17b2
|
701 |
Collections.sort(list); |
JM |
702 |
return list; |
|
703 |
} |
|
704 |
|
|
705 |
/** |
|
706 |
* Returns the list of commits separated by ref (e.g. each ref has it's own |
c134a0
|
707 |
* RefLogEntry object for each day). |
cf17b2
|
708 |
* |
JM |
709 |
* @param repositoryName |
|
710 |
* @param repository |
|
711 |
* @param minimumDate |
9b26b7
|
712 |
* @param the timezone to use when aggregating commits by date |
cf17b2
|
713 |
* @return a list of push log entries separated by ref and date |
JM |
714 |
*/ |
9b26b7
|
715 |
public static List<DailyLogEntry> getDailyLogByRef(String repositoryName, Repository repository, |
JM |
716 |
Date minimumDate, TimeZone timezone) { |
c134a0
|
717 |
// break the reflog into ref entries and then merge them back into a list |
cf17b2
|
718 |
Map<String, List<DailyLogEntry>> refMap = new HashMap<String, List<DailyLogEntry>>(); |
c134a0
|
719 |
List<DailyLogEntry> entries = getDailyLog(repositoryName, repository, minimumDate, 0, -1, timezone); |
JM |
720 |
for (DailyLogEntry entry : entries) { |
|
721 |
for (String ref : entry.getChangedRefs()) { |
cf17b2
|
722 |
if (!refMap.containsKey(ref)) { |
JM |
723 |
refMap.put(ref, new ArrayList<DailyLogEntry>()); |
|
724 |
} |
|
725 |
|
c134a0
|
726 |
// construct new ref-specific log entry |
JM |
727 |
DailyLogEntry refEntry = new DailyLogEntry(entry.repository, entry.date, entry.user); |
|
728 |
refEntry.updateRef(ref, entry.getChangeType(ref), entry.getOldId(ref), entry.getNewId(ref)); |
|
729 |
refEntry.addCommits(entry.getCommits(ref)); |
|
730 |
refMap.get(ref).add(refEntry); |
cf17b2
|
731 |
} |
JM |
732 |
} |
|
733 |
|
c134a0
|
734 |
// merge individual ref entries into master list |
JM |
735 |
List<DailyLogEntry> refLog = new ArrayList<DailyLogEntry>(); |
|
736 |
for (List<DailyLogEntry> refEntry : refMap.values()) { |
|
737 |
for (DailyLogEntry entry : refEntry) { |
cf17b2
|
738 |
if (entry.getCommitCount() > 0) { |
c134a0
|
739 |
refLog.add(entry); |
cf17b2
|
740 |
} |
JM |
741 |
} |
|
742 |
} |
|
743 |
|
c134a0
|
744 |
Collections.sort(refLog); |
cf17b2
|
745 |
|
c134a0
|
746 |
return refLog; |
cf17b2
|
747 |
} |
f8bb95
|
748 |
} |