Use the Git sort order.
[egit.git] / org.spearce.egit.core / src / org / spearce / egit / core / internal / mapping / GitFileHistory.java
blob0569862ac7140b51acc7c5bc3c95699cee67011b
1 /*
2 * Copyright (C) 2006 Robin Rosenberg
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License, version 2.1, as published by the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
17 package org.spearce.egit.core.internal.mapping;
19 import java.io.BufferedInputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.Date;
27 import java.util.List;
29 import org.eclipse.core.resources.IResource;
30 import org.eclipse.core.runtime.CoreException;
31 import org.eclipse.core.runtime.IAdaptable;
32 import org.eclipse.team.core.RepositoryProvider;
33 import org.eclipse.team.core.history.IFileHistoryProvider;
34 import org.eclipse.team.core.history.IFileRevision;
35 import org.eclipse.team.core.history.provider.FileHistory;
36 import org.spearce.egit.core.GitProvider;
37 import org.spearce.egit.core.GitWorkspaceFileRevision;
38 import org.spearce.egit.core.project.GitProjectData;
39 import org.spearce.egit.core.project.RepositoryMapping;
40 import org.spearce.jgit.lib.Commit;
41 import org.spearce.jgit.lib.ObjectId;
42 import org.spearce.jgit.lib.Repository;
43 import org.spearce.jgit.lib.Tree;
44 import org.spearce.jgit.lib.TreeEntry;
46 public class GitFileHistory extends FileHistory implements IAdaptable {
48 private final IResource resource;
50 private final String[] relativeResourceName;
52 private final int flags;
54 private IFileRevision[] revisions;
56 public GitFileHistory(IResource resource, int flags) {
57 this.resource = resource;
58 this.flags = flags;
59 String prefix = getRepositoryMapping().getSubset();
60 String[] prefixSegments = prefix!=null ? prefix.split("/") : new String[0];
61 String[] resourceSegments = resource.getProjectRelativePath().segments();
62 relativeResourceName = new String[prefixSegments.length + resourceSegments.length];
63 System.arraycopy(prefixSegments, 0, relativeResourceName, 0, prefixSegments.length);
64 System.arraycopy(resourceSegments, 0, relativeResourceName, prefixSegments.length, resourceSegments.length);
67 public IFileRevision[] getContributors(IFileRevision revision) {
68 GitFileRevision grevision = (GitFileRevision) revision;
69 List parents = grevision.getCommit().getParentIds();
70 IFileRevision[] ret = new IFileRevision[parents.size()];
71 Repository repository = getRepository();
72 for (int i = 0; i < parents.size(); ++i) {
73 try {
74 ret[i] = new GitFileRevision(repository
75 .mapCommit((ObjectId) parents.get(i)), grevision
76 .getResource(), -1);
77 } catch (IOException e) {
78 e.printStackTrace();
79 return null;
82 return ret;
85 public IFileRevision getFileRevision(String id) {
86 if (id.equals("")) {
87 return new GitWorkspaceFileRevision(resource);
88 } else {
89 try {
90 Repository repository = getRepository();
91 return new GitFileRevision(repository.mapCommit(id), resource, 0);
92 } catch (IOException e) {
93 e.printStackTrace();
94 return null;
99 private Repository getRepository() {
100 return getRepositoryMapping().getRepository();
103 private GitProjectData getData() {
104 GitProvider provider = (GitProvider) RepositoryProvider
105 .getProvider(resource.getProject());
106 return provider.getData();
109 private RepositoryMapping getRepositoryMapping() {
110 return getData().getRepositoryMapping(resource.getProject());
113 private Collection collectHistory() {
114 Repository repository = getRepository();
115 try {
116 ObjectId id = repository.resolve("HEAD");
117 Commit commit = repository.mapCommit(id);
118 ObjectId[] initialResourceHash = new ObjectId[relativeResourceName.length];
119 Arrays.fill(initialResourceHash, ObjectId.zeroId());
120 TreeEntry[] activeDiffTreeEntries = null;
121 try {
122 activeDiffTreeEntries = getData().getActiveDiffTreeEntries(resource);
123 } catch (CoreException e1) {
124 // TODO: eclipse excetion logging
125 e1.printStackTrace();
127 if (activeDiffTreeEntries!=null)
128 initialResourceHash[initialResourceHash.length-1] = activeDiffTreeEntries[0].getId();
129 return collectHistory(0, initialResourceHash, null,
130 repository, commit);
131 } catch (IOException e) {
132 e.printStackTrace();
133 return Collections.EMPTY_LIST;
137 private Collection collectHistory(int count, ObjectId[] lastResourceHash, TreeEntry lastEntry,
138 Repository repository, Commit top) throws IOException {
139 if (top == null)
140 return Collections.EMPTY_LIST;
141 Collection ret = new ArrayList(10000);
142 Commit current = top;
143 Commit previous = top;
145 do {
146 TreeEntry currentEntry = lastEntry;
147 ObjectId[] currentResourceHash = new ObjectId[lastResourceHash.length];
148 Tree t = current.getTree();
149 for (int i = 0; i < currentResourceHash.length; ++i) {
150 TreeEntry m;
151 if (i == relativeResourceName.length-1 && resource.getType() == IResource.FILE)
152 m = t.findBlobMember(relativeResourceName[i]);
153 else
154 m = t.findTreeMember(relativeResourceName[i]);
155 if (m != null) {
156 ObjectId id = m.getId();
157 currentResourceHash[i] = id;
158 if (id.equals(lastResourceHash[i])) {
159 while (++i < currentResourceHash.length) {
160 currentResourceHash[i] = lastResourceHash[i];
162 } else {
163 if (m instanceof Tree) {
164 t = (Tree)m;
165 } else {
166 if (i == currentResourceHash.length - 1) {
167 currentEntry = m;
168 } else {
169 currentEntry = null;
170 while (++i < currentResourceHash.length) {
171 currentResourceHash[i] = ObjectId.zeroId();
176 } else {
177 for (; i < currentResourceHash.length; ++i) {
178 currentResourceHash[i] = ObjectId.zeroId();
183 if (currentResourceHash.length == 0 || !currentResourceHash[currentResourceHash.length-1].equals(lastResourceHash[currentResourceHash.length-1]))
184 ret.add(new GitFileRevision(previous, resource, count));
186 lastResourceHash = currentResourceHash;
187 previous = current;
189 // TODO: we may need to list more revisions when traversing
190 // branches
191 List parents = current.getParentIds();
192 if ((flags & IFileHistoryProvider.SINGLE_LINE_OF_DESCENT) == 0) {
193 for (int i = 1; i < parents.size(); ++i) {
194 ObjectId mergeParentId = (ObjectId) parents.get(i);
195 Commit mergeParent;
196 try {
197 mergeParent = repository.mapCommit(mergeParentId);
198 ret.addAll(collectHistory(0, lastResourceHash, currentEntry, repository,
199 mergeParent));
200 // TODO: this gets us a lot of duplicates that we need
201 // to filter out
202 // Leave that til we get a GUI.
203 } catch (IOException e) {
204 e.printStackTrace();
208 if (parents.size() > 0) {
209 ObjectId parentId = (ObjectId) parents.get(0);
210 try {
211 current = repository.mapCommit(parentId);
212 } catch (IOException e) {
213 e.printStackTrace();
214 current = null;
216 } else
217 current = null;
218 if (count>=0)
219 count++;
220 } while (current != null);
222 return ret;
225 public IFileRevision[] getFileRevisions() {
226 if (revisions == null)
227 if ((flags & IFileHistoryProvider.SINGLE_LINE_OF_DESCENT) == 0)
228 findSingleRevision();
229 else
230 findRevisions();
231 return revisions;
235 * Get a single file revision suitable for quickdiff.
237 * We have two modes. For a branch set up for Stacked Git that has a patch
238 * return the revision prior to the topmost patch, be it another patch or a
239 * normal Git Commit. This is the revision in HEAD^. Otherwise we return the
240 * revision in HEAD.
242 private void findSingleRevision() {
243 try {
244 Repository repository = getRepository();
245 ObjectId id = repository.resolve("HEAD");
246 Commit current = repository.mapCommit(id);
247 if (repository.isStGitMode()) {
248 List parentIds = current.getParentIds();
249 if (parentIds != null && parentIds.size() > 0)
250 current = repository.mapCommit((ObjectId) parentIds.get(0));
251 else {
252 revisions = new IFileRevision[0];
253 return;
256 TreeEntry currentEntry = current.getTree();
257 for (int i=0; i < relativeResourceName.length && currentEntry != null; ++i) {
258 if (i == relativeResourceName.length-1 && resource.getType() == IResource.FILE)
259 ((Tree)currentEntry).findBlobMember(relativeResourceName[i]);
260 else
261 ((Tree)currentEntry).findTreeMember(relativeResourceName[i]);
263 if (currentEntry != null)
264 revisions = new IFileRevision[] { new GitFileRevision(current, resource,
265 -1) };
266 else
267 revisions = new IFileRevision[0];
269 } catch (IOException e) {
270 e.printStackTrace();
271 revisions = new IFileRevision[0];
275 private void findRevisions() {
276 RepositoryProvider provider = RepositoryProvider.getProvider(resource
277 .getProject());
278 if (provider instanceof GitProvider) {
279 GitWorkspaceFileRevision wsrevision = new GitWorkspaceFileRevision(resource);
281 long time0 = new Date().getTime();
282 System.out.println("getting file history");
283 List ret = new ArrayList();
284 Collection githistory = collectHistory();
285 if (githistory.size() >0) {
286 if (resource.getType()==IResource.FILE) {
287 // TODO: consider index in future versions
288 try {
289 InputStream wsContents = new BufferedInputStream(wsrevision.getStorage(null).getContents());
290 InputStream headContents = ((IFileRevision)githistory.toArray()[0]).getStorage(null).getContents();
291 if (!streamsEqual(wsContents,headContents)) {
292 ret.add(wsrevision);
293 ret.addAll(githistory);
294 } else {
295 ret.addAll(githistory);
297 wsContents.close();
298 headContents.close();
299 } catch (IOException e) {
300 // TODO: Eclipse error handling
301 e.printStackTrace();
302 } catch (CoreException e) {
303 // TODO: Eclipse error handling
304 e.printStackTrace();
306 } else {
307 ret.addAll(githistory);
309 } else {
310 ret.add(wsrevision);
312 long time1 = new Date().getTime();
313 System.out.println("got file history in " + (time1 - time0)
314 / 1000.0 + "s");
316 revisions = (IFileRevision[]) ret.toArray(new IFileRevision[ret
317 .size()]);
319 } else {
320 revisions = new IFileRevision[0];
324 private boolean streamsEqual(InputStream s1, InputStream s2) {
325 // Speed up...
326 try {
327 int c1,c2;
328 while ((c1=s1.read()) == (c2=s2.read()) && c1!=-1)
330 return c1 == -1 && c2==-1;
331 } catch (IOException e) {
332 // TODO: eclipse error handling
333 e.printStackTrace();
334 return false;
338 public IFileRevision[] getTargets(IFileRevision revision) {
339 GitFileRevision grevision = (GitFileRevision) revision;
340 ObjectId targetCommitId = grevision.getCommit().getCommitId();
341 List ret = new ArrayList(4);
342 for (int i = 0; i < revisions.length; ++i) {
343 Commit ref = ((GitFileRevision) revisions[i]).getCommit();
344 List parentIds = ref.getParentIds();
345 if (parentIds.contains(targetCommitId)) {
346 ret.add(revisions[i]);
349 return (IFileRevision[]) ret.toArray(new IFileRevision[ret.size()]);
352 public Object getAdapter(Class adapter) {
353 System.out.println("GitFileHistory.getAdapter "+adapter.getName());
354 return null;