You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RefSpec.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. /*
  2. * Copyright (C) 2008, 2013 Shawn O. Pearce <spearce@spearce.org>
  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. package org.eclipse.jgit.transport;
  44. import java.io.Serializable;
  45. import java.text.MessageFormat;
  46. import org.eclipse.jgit.internal.JGitText;
  47. import org.eclipse.jgit.lib.Constants;
  48. import org.eclipse.jgit.lib.Ref;
  49. /**
  50. * Describes how refs in one repository copy into another repository.
  51. * <p>
  52. * A ref specification provides matching support and limited rules to rewrite a
  53. * reference in one repository to another reference in another repository.
  54. */
  55. public class RefSpec implements Serializable {
  56. private static final long serialVersionUID = 1L;
  57. /**
  58. * Suffix for wildcard ref spec component, that indicate matching all refs
  59. * with specified prefix.
  60. */
  61. public static final String WILDCARD_SUFFIX = "/*"; //$NON-NLS-1$
  62. /**
  63. * Check whether provided string is a wildcard ref spec component.
  64. *
  65. * @param s
  66. * ref spec component - string to test. Can be null.
  67. * @return true if provided string is a wildcard ref spec component.
  68. */
  69. public static boolean isWildcard(final String s) {
  70. return s != null && s.contains("*"); //$NON-NLS-1$
  71. }
  72. /** Does this specification ask for forced updated (rewind/reset)? */
  73. private boolean force;
  74. /** Is this specification actually a wildcard match? */
  75. private boolean wildcard;
  76. /** Name of the ref(s) we would copy from. */
  77. private String srcName;
  78. /** Name of the ref(s) we would copy into. */
  79. private String dstName;
  80. /**
  81. * Construct an empty RefSpec.
  82. * <p>
  83. * A newly created empty RefSpec is not suitable for use in most
  84. * applications, as at least one field must be set to match a source name.
  85. */
  86. public RefSpec() {
  87. force = false;
  88. wildcard = false;
  89. srcName = Constants.HEAD;
  90. dstName = null;
  91. }
  92. /**
  93. * Parse a ref specification for use during transport operations.
  94. * <p>
  95. * Specifications are typically one of the following forms:
  96. * <ul>
  97. * <li><code>refs/heads/master</code></li>
  98. * <li><code>refs/heads/master:refs/remotes/origin/master</code></li>
  99. * <li><code>refs/heads/*:refs/remotes/origin/*</code></li>
  100. * <li><code>+refs/heads/master</code></li>
  101. * <li><code>+refs/heads/master:refs/remotes/origin/master</code></li>
  102. * <li><code>+refs/heads/*:refs/remotes/origin/*</code></li>
  103. * <li><code>+refs/pull/&#42;/head:refs/remotes/origin/pr/*</code></li>
  104. * <li><code>:refs/heads/master</code></li>
  105. * </ul>
  106. *
  107. * @param spec
  108. * string describing the specification.
  109. * @throws IllegalArgumentException
  110. * the specification is invalid.
  111. */
  112. public RefSpec(final String spec) {
  113. String s = spec;
  114. if (s.startsWith("+")) { //$NON-NLS-1$
  115. force = true;
  116. s = s.substring(1);
  117. }
  118. final int c = s.lastIndexOf(':');
  119. if (c == 0) {
  120. s = s.substring(1);
  121. if (isWildcard(s))
  122. throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec));
  123. dstName = checkValid(s);
  124. } else if (c > 0) {
  125. String src = s.substring(0, c);
  126. String dst = s.substring(c + 1);
  127. if (isWildcard(src) && isWildcard(dst)) {
  128. // Both contain wildcard
  129. wildcard = true;
  130. } else if (isWildcard(src) || isWildcard(dst)) {
  131. // If either source or destination has wildcard, the other one
  132. // must have as well.
  133. throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec));
  134. }
  135. srcName = checkValid(src);
  136. dstName = checkValid(dst);
  137. } else {
  138. if (isWildcard(s))
  139. throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec));
  140. srcName = checkValid(s);
  141. }
  142. }
  143. private RefSpec(final RefSpec p) {
  144. force = p.isForceUpdate();
  145. wildcard = p.isWildcard();
  146. srcName = p.getSource();
  147. dstName = p.getDestination();
  148. }
  149. /**
  150. * Check if this specification wants to forcefully update the destination.
  151. *
  152. * @return true if this specification asks for updates without merge tests.
  153. */
  154. public boolean isForceUpdate() {
  155. return force;
  156. }
  157. /**
  158. * Create a new RefSpec with a different force update setting.
  159. *
  160. * @param forceUpdate
  161. * new value for force update in the returned instance.
  162. * @return a new RefSpec with force update as specified.
  163. */
  164. public RefSpec setForceUpdate(final boolean forceUpdate) {
  165. final RefSpec r = new RefSpec(this);
  166. r.force = forceUpdate;
  167. return r;
  168. }
  169. /**
  170. * Check if this specification is actually a wildcard pattern.
  171. * <p>
  172. * If this is a wildcard pattern then the source and destination names
  173. * returned by {@link #getSource()} and {@link #getDestination()} will not
  174. * be actual ref names, but instead will be patterns.
  175. *
  176. * @return true if this specification could match more than one ref.
  177. */
  178. public boolean isWildcard() {
  179. return wildcard;
  180. }
  181. /**
  182. * Get the source ref description.
  183. * <p>
  184. * During a fetch this is the name of the ref on the remote repository we
  185. * are fetching from. During a push this is the name of the ref on the local
  186. * repository we are pushing out from.
  187. *
  188. * @return name (or wildcard pattern) to match the source ref.
  189. */
  190. public String getSource() {
  191. return srcName;
  192. }
  193. /**
  194. * Create a new RefSpec with a different source name setting.
  195. *
  196. * @param source
  197. * new value for source in the returned instance.
  198. * @return a new RefSpec with source as specified.
  199. * @throws IllegalStateException
  200. * There is already a destination configured, and the wildcard
  201. * status of the existing destination disagrees with the
  202. * wildcard status of the new source.
  203. */
  204. public RefSpec setSource(final String source) {
  205. final RefSpec r = new RefSpec(this);
  206. r.srcName = checkValid(source);
  207. if (isWildcard(r.srcName) && r.dstName == null)
  208. throw new IllegalStateException(JGitText.get().destinationIsNotAWildcard);
  209. if (isWildcard(r.srcName) != isWildcard(r.dstName))
  210. throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
  211. return r;
  212. }
  213. /**
  214. * Get the destination ref description.
  215. * <p>
  216. * During a fetch this is the local tracking branch that will be updated
  217. * with the new ObjectId after fetching is complete. During a push this is
  218. * the remote ref that will be updated by the remote's receive-pack process.
  219. * <p>
  220. * If null during a fetch no tracking branch should be updated and the
  221. * ObjectId should be stored transiently in order to prepare a merge.
  222. * <p>
  223. * If null during a push, use {@link #getSource()} instead.
  224. *
  225. * @return name (or wildcard) pattern to match the destination ref.
  226. */
  227. public String getDestination() {
  228. return dstName;
  229. }
  230. /**
  231. * Create a new RefSpec with a different destination name setting.
  232. *
  233. * @param destination
  234. * new value for destination in the returned instance.
  235. * @return a new RefSpec with destination as specified.
  236. * @throws IllegalStateException
  237. * There is already a source configured, and the wildcard status
  238. * of the existing source disagrees with the wildcard status of
  239. * the new destination.
  240. */
  241. public RefSpec setDestination(final String destination) {
  242. final RefSpec r = new RefSpec(this);
  243. r.dstName = checkValid(destination);
  244. if (isWildcard(r.dstName) && r.srcName == null)
  245. throw new IllegalStateException(JGitText.get().sourceIsNotAWildcard);
  246. if (isWildcard(r.srcName) != isWildcard(r.dstName))
  247. throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
  248. return r;
  249. }
  250. /**
  251. * Create a new RefSpec with a different source/destination name setting.
  252. *
  253. * @param source
  254. * new value for source in the returned instance.
  255. * @param destination
  256. * new value for destination in the returned instance.
  257. * @return a new RefSpec with destination as specified.
  258. * @throws IllegalArgumentException
  259. * The wildcard status of the new source disagrees with the
  260. * wildcard status of the new destination.
  261. */
  262. public RefSpec setSourceDestination(final String source,
  263. final String destination) {
  264. if (isWildcard(source) != isWildcard(destination))
  265. throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
  266. final RefSpec r = new RefSpec(this);
  267. r.wildcard = isWildcard(source);
  268. r.srcName = source;
  269. r.dstName = destination;
  270. return r;
  271. }
  272. /**
  273. * Does this specification's source description match the ref name?
  274. *
  275. * @param r
  276. * ref name that should be tested.
  277. * @return true if the names match; false otherwise.
  278. */
  279. public boolean matchSource(final String r) {
  280. return match(r, getSource());
  281. }
  282. /**
  283. * Does this specification's source description match the ref?
  284. *
  285. * @param r
  286. * ref whose name should be tested.
  287. * @return true if the names match; false otherwise.
  288. */
  289. public boolean matchSource(final Ref r) {
  290. return match(r.getName(), getSource());
  291. }
  292. /**
  293. * Does this specification's destination description match the ref name?
  294. *
  295. * @param r
  296. * ref name that should be tested.
  297. * @return true if the names match; false otherwise.
  298. */
  299. public boolean matchDestination(final String r) {
  300. return match(r, getDestination());
  301. }
  302. /**
  303. * Does this specification's destination description match the ref?
  304. *
  305. * @param r
  306. * ref whose name should be tested.
  307. * @return true if the names match; false otherwise.
  308. */
  309. public boolean matchDestination(final Ref r) {
  310. return match(r.getName(), getDestination());
  311. }
  312. /**
  313. * Expand this specification to exactly match a ref name.
  314. * <p>
  315. * Callers must first verify the passed ref name matches this specification,
  316. * otherwise expansion results may be unpredictable.
  317. *
  318. * @param r
  319. * a ref name that matched our source specification. Could be a
  320. * wildcard also.
  321. * @return a new specification expanded from provided ref name. Result
  322. * specification is wildcard if and only if provided ref name is
  323. * wildcard.
  324. */
  325. public RefSpec expandFromSource(final String r) {
  326. return isWildcard() ? new RefSpec(this).expandFromSourceImp(r) : this;
  327. }
  328. private RefSpec expandFromSourceImp(final String name) {
  329. final String psrc = srcName, pdst = dstName;
  330. wildcard = false;
  331. srcName = name;
  332. dstName = expandWildcard(name, psrc, pdst);
  333. return this;
  334. }
  335. /**
  336. * Expand this specification to exactly match a ref.
  337. * <p>
  338. * Callers must first verify the passed ref matches this specification,
  339. * otherwise expansion results may be unpredictable.
  340. *
  341. * @param r
  342. * a ref that matched our source specification. Could be a
  343. * wildcard also.
  344. * @return a new specification expanded from provided ref name. Result
  345. * specification is wildcard if and only if provided ref name is
  346. * wildcard.
  347. */
  348. public RefSpec expandFromSource(final Ref r) {
  349. return expandFromSource(r.getName());
  350. }
  351. /**
  352. * Expand this specification to exactly match a ref name.
  353. * <p>
  354. * Callers must first verify the passed ref name matches this specification,
  355. * otherwise expansion results may be unpredictable.
  356. *
  357. * @param r
  358. * a ref name that matched our destination specification. Could
  359. * be a wildcard also.
  360. * @return a new specification expanded from provided ref name. Result
  361. * specification is wildcard if and only if provided ref name is
  362. * wildcard.
  363. */
  364. public RefSpec expandFromDestination(final String r) {
  365. return isWildcard() ? new RefSpec(this).expandFromDstImp(r) : this;
  366. }
  367. private RefSpec expandFromDstImp(final String name) {
  368. final String psrc = srcName, pdst = dstName;
  369. wildcard = false;
  370. srcName = expandWildcard(name, pdst, psrc);
  371. dstName = name;
  372. return this;
  373. }
  374. /**
  375. * Expand this specification to exactly match a ref.
  376. * <p>
  377. * Callers must first verify the passed ref matches this specification,
  378. * otherwise expansion results may be unpredictable.
  379. *
  380. * @param r
  381. * a ref that matched our destination specification.
  382. * @return a new specification expanded from provided ref name. Result
  383. * specification is wildcard if and only if provided ref name is
  384. * wildcard.
  385. */
  386. public RefSpec expandFromDestination(final Ref r) {
  387. return expandFromDestination(r.getName());
  388. }
  389. private boolean match(final String name, final String s) {
  390. if (s == null)
  391. return false;
  392. if (isWildcard()) {
  393. int wildcardIndex = s.indexOf('*');
  394. String prefix = s.substring(0, wildcardIndex);
  395. String suffix = s.substring(wildcardIndex + 1);
  396. return name.length() > prefix.length() + suffix.length()
  397. && name.startsWith(prefix) && name.endsWith(suffix);
  398. }
  399. return name.equals(s);
  400. }
  401. private static String expandWildcard(String name, String patternA,
  402. String patternB) {
  403. int a = patternA.indexOf('*');
  404. int trailingA = patternA.length() - (a + 1);
  405. int b = patternB.indexOf('*');
  406. String match = name.substring(a, name.length() - trailingA);
  407. return patternB.substring(0, b) + match + patternB.substring(b + 1);
  408. }
  409. private static String checkValid(String spec) {
  410. if (spec != null && !isValid(spec))
  411. throw new IllegalArgumentException(MessageFormat.format(
  412. JGitText.get().invalidRefSpec, spec));
  413. return spec;
  414. }
  415. private static boolean isValid(final String s) {
  416. if (s.startsWith("/")) //$NON-NLS-1$
  417. return false;
  418. if (s.contains("//")) //$NON-NLS-1$
  419. return false;
  420. if (s.endsWith("/")) //$NON-NLS-1$
  421. return false;
  422. int i = s.indexOf('*');
  423. if (i != -1) {
  424. if (s.indexOf('*', i + 1) > i)
  425. return false;
  426. }
  427. return true;
  428. }
  429. public int hashCode() {
  430. int hc = 0;
  431. if (getSource() != null)
  432. hc = hc * 31 + getSource().hashCode();
  433. if (getDestination() != null)
  434. hc = hc * 31 + getDestination().hashCode();
  435. return hc;
  436. }
  437. public boolean equals(final Object obj) {
  438. if (!(obj instanceof RefSpec))
  439. return false;
  440. final RefSpec b = (RefSpec) obj;
  441. if (isForceUpdate() != b.isForceUpdate())
  442. return false;
  443. if (isWildcard() != b.isWildcard())
  444. return false;
  445. if (!eq(getSource(), b.getSource()))
  446. return false;
  447. if (!eq(getDestination(), b.getDestination()))
  448. return false;
  449. return true;
  450. }
  451. private static boolean eq(final String a, final String b) {
  452. if (a == b)
  453. return true;
  454. if (a == null || b == null)
  455. return false;
  456. return a.equals(b);
  457. }
  458. public String toString() {
  459. final StringBuilder r = new StringBuilder();
  460. if (isForceUpdate())
  461. r.append('+');
  462. if (getSource() != null)
  463. r.append(getSource());
  464. if (getDestination() != null) {
  465. r.append(':');
  466. r.append(getDestination());
  467. }
  468. return r.toString();
  469. }
  470. }