View Javadoc
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 }