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 }