1 /*
2 * Copyright (C) 2008-2010, Google Inc.
3 * and other copyright owners as documented in the project's IP log.
4 *
5 * This program and the accompanying materials are made available
6 * under the terms of the Eclipse Distribution License v1.0 which
7 * accompanies this distribution, is reproduced below, and is
8 * available at http://www.eclipse.org/org/documents/edl-v10.php
9 *
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
14 * conditions are met:
15 *
16 * - Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 *
19 * - Redistributions in binary form must reproduce the above
20 * copyright notice, this list of conditions and the following
21 * disclaimer in the documentation and/or other materials provided
22 * with the distribution.
23 *
24 * - Neither the name of the Eclipse Foundation, Inc. nor the
25 * names of its contributors may be used to endorse or promote
26 * products derived from this software without specific prior
27 * written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 */
43
44 package org.eclipse.jgit.transport;
45
46 import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SYMREF;
47
48 import java.io.IOException;
49 import java.util.HashSet;
50 import java.util.LinkedHashSet;
51 import java.util.Map;
52 import java.util.Set;
53 import java.util.SortedMap;
54
55 import org.eclipse.jgit.lib.AnyObjectId;
56 import org.eclipse.jgit.lib.Constants;
57 import org.eclipse.jgit.lib.ObjectId;
58 import org.eclipse.jgit.lib.Ref;
59 import org.eclipse.jgit.lib.RefComparator;
60 import org.eclipse.jgit.lib.Repository;
61 import org.eclipse.jgit.util.RefMap;
62
63 /** Support for the start of {@link UploadPack} and {@link ReceivePack}. */
64 public abstract class RefAdvertiser {
65 /** Advertiser which frames lines in a {@link PacketLineOut} format. */
66 public static class PacketLineOutRefAdvertiser extends RefAdvertiser {
67 private final PacketLineOut pckOut;
68
69 /**
70 * Create a new advertiser for the supplied stream.
71 *
72 * @param out
73 * the output stream.
74 */
75 public PacketLineOutRefAdvertiser(PacketLineOut out) {
76 pckOut = out;
77 }
78
79 @Override
80 protected void writeOne(final CharSequence line) throws IOException {
81 pckOut.writeString(line.toString());
82 }
83
84 @Override
85 protected void end() throws IOException {
86 pckOut.end();
87 }
88 }
89
90 private final StringBuilder tmpLine = new StringBuilder(100);
91
92 private final char[] tmpId = new char[Constants.OBJECT_ID_STRING_LENGTH];
93
94 private final Set<String> capablities = new LinkedHashSet<String>();
95
96 private final Set<ObjectId> sent = new HashSet<ObjectId>();
97
98 private Repository repository;
99
100 private boolean derefTags;
101
102 private boolean first = true;
103
104 /**
105 * Initialize this advertiser with a repository for peeling tags.
106 *
107 * @param src
108 * the repository to read from.
109 */
110 public void init(Repository src) {
111 repository = src;
112 }
113
114 /**
115 * Toggle tag peeling.
116 * <p>
117 * <p>
118 * This method must be invoked prior to any of the following:
119 * <ul>
120 * <li>{@link #send(Map)}
121 * </ul>
122 *
123 * @param deref
124 * true to show the dereferenced value of a tag as the special
125 * ref <code>$tag^{}</code> ; false to omit it from the output.
126 */
127 public void setDerefTags(final boolean deref) {
128 derefTags = deref;
129 }
130
131 /**
132 * Add one protocol capability to the initial advertisement.
133 * <p>
134 * This method must be invoked prior to any of the following:
135 * <ul>
136 * <li>{@link #send(Map)}
137 * <li>{@link #advertiseHave(AnyObjectId)}
138 * </ul>
139 *
140 * @param name
141 * the name of a single protocol capability supported by the
142 * caller. The set of capabilities are sent to the client in the
143 * advertisement, allowing the client to later selectively enable
144 * features it recognizes.
145 */
146 public void advertiseCapability(String name) {
147 capablities.add(name);
148 }
149
150 /**
151 * Add one protocol capability with a value ({@code "name=value"}).
152 *
153 * @param name
154 * name of the capability.
155 * @param value
156 * value. If null the capability will not be added.
157 * @since 4.0
158 */
159 public void advertiseCapability(String name, String value) {
160 if (value != null) {
161 capablities.add(name + '=' + value);
162 }
163 }
164
165 /**
166 * Add a symbolic ref to capabilities.
167 * <p>
168 * This method must be invoked prior to any of the following:
169 * <ul>
170 * <li>{@link #send(Map)}
171 * <li>{@link #advertiseHave(AnyObjectId)}
172 * </ul>
173 *
174 * @param from
175 * The symbolic ref, e.g. "HEAD"
176 * @param to
177 * The real ref it points to, e.g. "refs/heads/master"
178 *
179 * @since 3.6
180 */
181 public void addSymref(String from, String to) {
182 advertiseCapability(OPTION_SYMREF, from + ':' + to);
183 }
184
185 /**
186 * Format an advertisement for the supplied refs.
187 *
188 * @param refs
189 * zero or more refs to format for the client. The collection is
190 * sorted before display if necessary, and therefore may appear
191 * in any order.
192 * @return set of ObjectIds that were advertised to the client.
193 * @throws IOException
194 * the underlying output stream failed to write out an
195 * advertisement record.
196 */
197 public Set<ObjectId> send(Map<String, Ref> refs) throws IOException {
198 for (Ref ref : getSortedRefs(refs)) {
199 if (ref.getObjectId() == null)
200 continue;
201
202 advertiseAny(ref.getObjectId(), ref.getName());
203
204 if (!derefTags)
205 continue;
206
207 if (!ref.isPeeled()) {
208 if (repository == null)
209 continue;
210 ref = repository.peel(ref);
211 }
212
213 if (ref.getPeeledObjectId() != null)
214 advertiseAny(ref.getPeeledObjectId(), ref.getName() + "^{}"); //$NON-NLS-1$
215 }
216 return sent;
217 }
218
219 private Iterable<Ref> getSortedRefs(Map<String, Ref> all) {
220 if (all instanceof RefMap
221 || (all instanceof SortedMap && ((SortedMap) all).comparator() == null))
222 return all.values();
223 return RefComparator.sort(all.values());
224 }
225
226 /**
227 * Advertise one object is available using the magic {@code .have}.
228 * <p>
229 * The magic {@code .have} advertisement is not available for fetching by a
230 * client, but can be used by a client when considering a delta base
231 * candidate before transferring data in a push. Within the record created
232 * by this method the ref name is simply the invalid string {@code .have}.
233 *
234 * @param id
235 * identity of the object that is assumed to exist.
236 * @throws IOException
237 * the underlying output stream failed to write out an
238 * advertisement record.
239 */
240 public void advertiseHave(AnyObjectId id) throws IOException {
241 advertiseAnyOnce(id, ".have"); //$NON-NLS-1$
242 }
243
244 /** @return true if no advertisements have been sent yet. */
245 public boolean isEmpty() {
246 return first;
247 }
248
249 private void advertiseAnyOnce(AnyObjectId obj, final String refName)
250 throws IOException {
251 if (!sent.contains(obj))
252 advertiseAny(obj, refName);
253 }
254
255 private void advertiseAny(AnyObjectId obj, final String refName)
256 throws IOException {
257 sent.add(obj.toObjectId());
258 advertiseId(obj, refName);
259 }
260
261 /**
262 * Advertise one object under a specific name.
263 * <p>
264 * If the advertised object is a tag, this method does not advertise the
265 * peeled version of it.
266 *
267 * @param id
268 * the object to advertise.
269 * @param refName
270 * name of the reference to advertise the object as, can be any
271 * string not including the NUL byte.
272 * @throws IOException
273 * the underlying output stream failed to write out an
274 * advertisement record.
275 */
276 public void advertiseId(final AnyObjectId id, final String refName)
277 throws IOException {
278 tmpLine.setLength(0);
279 id.copyTo(tmpId, tmpLine);
280 tmpLine.append(' ');
281 tmpLine.append(refName);
282 if (first) {
283 first = false;
284 if (!capablities.isEmpty()) {
285 tmpLine.append('\0');
286 for (final String capName : capablities) {
287 tmpLine.append(' ');
288 tmpLine.append(capName);
289 }
290 tmpLine.append(' ');
291 }
292 }
293 tmpLine.append('\n');
294 writeOne(tmpLine);
295 }
296
297 /**
298 * Write a single advertisement line.
299 *
300 * @param line
301 * the advertisement line to be written. The line always ends
302 * with LF. Never null or the empty string.
303 * @throws IOException
304 * the underlying output stream failed to write out an
305 * advertisement record.
306 */
307 protected abstract void writeOne(CharSequence line) throws IOException;
308
309 /**
310 * Mark the end of the advertisements.
311 *
312 * @throws IOException
313 * the underlying output stream failed to write out an
314 * advertisement record.
315 */
316 protected abstract void end() throws IOException;
317 }