View Javadoc
1   /*
2    * Copyright (C) 2008-2018, Robin Rosenberg <robin.rosenberg@dewire.com>
3    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
4    *
5    * This program and the accompanying materials are made available under the
6    * terms of the Eclipse Distribution License v. 1.0 which is available at
7    * https://www.eclipse.org/org/documents/edl-v10.php.
8    *
9    * SPDX-License-Identifier: BSD-3-Clause
10   */
11  
12  package org.eclipse.jgit.revplot;
13  
14  import static org.eclipse.jgit.lib.Constants.R_HEADS;
15  import static org.eclipse.jgit.lib.Constants.R_REMOTES;
16  import static org.eclipse.jgit.lib.Constants.R_TAGS;
17  
18  import java.io.IOException;
19  import java.util.Arrays;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.Comparator;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
29  import org.eclipse.jgit.errors.MissingObjectException;
30  import org.eclipse.jgit.internal.JGitText;
31  import org.eclipse.jgit.lib.AnyObjectId;
32  import org.eclipse.jgit.lib.PersonIdent;
33  import org.eclipse.jgit.lib.Ref;
34  import org.eclipse.jgit.lib.Repository;
35  import org.eclipse.jgit.revwalk.RevCommit;
36  import org.eclipse.jgit.revwalk.RevObject;
37  import org.eclipse.jgit.revwalk.RevSort;
38  import org.eclipse.jgit.revwalk.RevTag;
39  import org.eclipse.jgit.revwalk.RevWalk;
40  
41  /**
42   * Specialized RevWalk for visualization of a commit graph.
43   */
44  public class PlotWalk extends RevWalk {
45  
46  	private Map<AnyObjectId, Set<Ref>> additionalRefMap;
47  
48  	private Map<AnyObjectId, Set<Ref>> reverseRefMap;
49  
50  	private Repository repository;
51  
52  	/** {@inheritDoc} */
53  	@Override
54  	public void dispose() {
55  		super.dispose();
56  		if (reverseRefMap != null) {
57  			reverseRefMap.clear();
58  			reverseRefMap = null;
59  		}
60  		if (additionalRefMap != null) {
61  			additionalRefMap.clear();
62  			additionalRefMap = null;
63  		}
64  		repository = null;
65  	}
66  
67  	/**
68  	 * Create a new revision walker for a given repository.
69  	 *
70  	 * @param repo
71  	 *            the repository the walker will obtain data from.
72  	 */
73  	public PlotWalk(Repository repo) {
74  		super(repo);
75  		super.sort(RevSort.TOPO, true);
76  		additionalRefMap = new HashMap<>();
77  		repository = repo;
78  	}
79  
80  	/**
81  	 * Add additional refs to the walk
82  	 *
83  	 * @param refs
84  	 *            additional refs
85  	 * @throws java.io.IOException
86  	 */
87  	public void addAdditionalRefs(Iterable<Ref> refs) throws IOException {
88  		for (Ref ref : refs) {
89  			Set<Ref> set = additionalRefMap.get(ref.getObjectId());
90  			if (set == null)
91  				set = Collections.singleton(ref);
92  			else {
93  				set = new HashSet<>(set);
94  				set.add(ref);
95  			}
96  			additionalRefMap.put(ref.getObjectId(), set);
97  		}
98  	}
99  
100 	/** {@inheritDoc} */
101 	@Override
102 	public void sort(RevSort s, boolean use) {
103 		if (s == RevSort.TOPO && !use)
104 			throw new IllegalArgumentException(JGitText.get().topologicalSortRequired);
105 		super.sort(s, use);
106 	}
107 
108 	/** {@inheritDoc} */
109 	@Override
110 	protected RevCommit createCommit(AnyObjectId id) {
111 		return new PlotCommit(id);
112 	}
113 
114 	/** {@inheritDoc} */
115 	@Override
116 	public RevCommit next() throws MissingObjectException,
117 			IncorrectObjectTypeException, IOException {
118 		PlotCommit<?> pc = (PlotCommit) super.next();
119 		if (pc != null)
120 			pc.refs = getRefs(pc);
121 		return pc;
122 	}
123 
124 	private Ref[] getRefs(AnyObjectId commitId) {
125 		if (reverseRefMap == null) {
126 			reverseRefMap = repository.getAllRefsByPeeledObjectId();
127 			for (Map.Entry<AnyObjectId, Set<Ref>> entry : additionalRefMap
128 					.entrySet()) {
129 				Set<Ref> set = reverseRefMap.get(entry.getKey());
130 				Set<Ref> additional = entry.getValue();
131 				if (set != null) {
132 					if (additional.size() == 1) {
133 						// It's an unmodifiable singleton set...
134 						additional = new HashSet<>(additional);
135 					}
136 					additional.addAll(set);
137 				}
138 				reverseRefMap.put(entry.getKey(), additional);
139 			}
140 			additionalRefMap.clear();
141 			additionalRefMap = null;
142 		}
143 		Collection<Ref> list = reverseRefMap.get(commitId);
144 		if (list == null) {
145 			return PlotCommit.NO_REFS;
146 		}
147 		Ref[] tags = list.toArray(new Ref[0]);
148 		Arrays.sort(tags, new PlotRefComparator());
149 		return tags;
150 	}
151 
152 	class PlotRefComparator implements Comparator<Ref> {
153 		@Override
154 		public int compare(Ref o1, Ref o2) {
155 			try {
156 				RevObject obj1 = parseAny(o1.getObjectId());
157 				RevObject obj2 = parseAny(o2.getObjectId());
158 				long t1 = timeof(obj1);
159 				long t2 = timeof(obj2);
160 				if (t1 > t2)
161 					return -1;
162 				if (t1 < t2)
163 					return 1;
164 			} catch (IOException e) {
165 				// ignore
166 			}
167 
168 			int cmp = kind(o1) - kind(o2);
169 			if (cmp == 0)
170 				cmp = o1.getName().compareTo(o2.getName());
171 			return cmp;
172 		}
173 
174 		long timeof(RevObject o) {
175 			if (o instanceof RevCommit)
176 				return ((RevCommit) o).getCommitTime();
177 			if (o instanceof RevTag) {
178 				RevTag tag = (RevTag) o;
179 				try {
180 					parseBody(tag);
181 				} catch (IOException e) {
182 					return 0;
183 				}
184 				PersonIdent who = tag.getTaggerIdent();
185 				return who != null ? who.getWhen().getTime() : 0;
186 			}
187 			return 0;
188 		}
189 
190 		int kind(Ref r) {
191 			if (r.getName().startsWith(R_TAGS))
192 				return 0;
193 			if (r.getName().startsWith(R_HEADS))
194 				return 1;
195 			if (r.getName().startsWith(R_REMOTES))
196 				return 2;
197 			return 3;
198 		}
199 	}
200 }