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 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  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. enum WildcardMode {
  77. REQUIRE_MATCH, ALLOW_MISMATCH
  78. }
  79. /** Whether a wildcard is allowed on one side but not the other. */
  80. private WildcardMode allowMismatchedWildcards;
  81. /** Name of the ref(s) we would copy from. */
  82. private String srcName;
  83. /** Name of the ref(s) we would copy into. */
  84. private String dstName;
  85. /**
  86. * Construct an empty RefSpec.
  87. * <p>
  88. * A newly created empty RefSpec is not suitable for use in most
  89. * applications, as at least one field must be set to match a source name.
  90. */
  91. public RefSpec() {
  92. force = false;
  93. wildcard = false;
  94. srcName = Constants.HEAD;
  95. dstName = null;
  96. allowMismatchedWildcards = WildcardMode.REQUIRE_MATCH;
  97. }
  98. /**
  99. * Parse a ref specification for use during transport operations.
  100. * <p>
  101. * Specifications are typically one of the following forms:
  102. * <ul>
  103. * <li><code>refs/heads/master</code></li>
  104. * <li><code>refs/heads/master:refs/remotes/origin/master</code></li>
  105. * <li><code>refs/heads/*:refs/remotes/origin/*</code></li>
  106. * <li><code>+refs/heads/master</code></li>
  107. * <li><code>+refs/heads/master:refs/remotes/origin/master</code></li>
  108. * <li><code>+refs/heads/*:refs/remotes/origin/*</code></li>
  109. * <li><code>+refs/pull/&#42;/head:refs/remotes/origin/pr/*</code></li>
  110. * <li><code>:refs/heads/master</code></li>
  111. * </ul>
  112. *
  113. * If the wildcard mode allows mismatches, then these ref specs are also
  114. * valid:
  115. * <ul>
  116. * <li><code>refs/heads/*</code></li>
  117. * <li><code>refs/heads/*:refs/heads/master</code></li>
  118. * </ul>
  119. *
  120. * @param spec
  121. * string describing the specification.
  122. * @param mode
  123. * whether to allow a wildcard on one side without a wildcard on
  124. * the other.
  125. * @throws IllegalArgumentException
  126. * the specification is invalid.
  127. * @since 4.5
  128. */
  129. public RefSpec(String spec, WildcardMode mode) {
  130. this.allowMismatchedWildcards = mode;
  131. String s = spec;
  132. if (s.startsWith("+")) { //$NON-NLS-1$
  133. force = true;
  134. s = s.substring(1);
  135. }
  136. final int c = s.lastIndexOf(':');
  137. if (c == 0) {
  138. s = s.substring(1);
  139. if (isWildcard(s)) {
  140. wildcard = true;
  141. if (mode == WildcardMode.REQUIRE_MATCH) {
  142. throw new IllegalArgumentException(MessageFormat
  143. .format(JGitText.get().invalidWildcards, spec));
  144. }
  145. }
  146. dstName = checkValid(s);
  147. } else if (c > 0) {
  148. String src = s.substring(0, c);
  149. String dst = s.substring(c + 1);
  150. if (isWildcard(src) && isWildcard(dst)) {
  151. // Both contain wildcard
  152. wildcard = true;
  153. } else if (isWildcard(src) || isWildcard(dst)) {
  154. wildcard = true;
  155. if (mode == WildcardMode.REQUIRE_MATCH)
  156. throw new IllegalArgumentException(MessageFormat
  157. .format(JGitText.get().invalidWildcards, spec));
  158. }
  159. srcName = checkValid(src);
  160. dstName = checkValid(dst);
  161. } else {
  162. if (isWildcard(s)) {
  163. if (mode == WildcardMode.REQUIRE_MATCH) {
  164. throw new IllegalArgumentException(MessageFormat
  165. .format(JGitText.get().invalidWildcards, spec));
  166. }
  167. wildcard = true;
  168. }
  169. srcName = checkValid(s);
  170. }
  171. }
  172. /**
  173. * Parse a ref specification for use during transport operations.
  174. * <p>
  175. * Specifications are typically one of the following forms:
  176. * <ul>
  177. * <li><code>refs/heads/master</code></li>
  178. * <li><code>refs/heads/master:refs/remotes/origin/master</code></li>
  179. * <li><code>refs/heads/*:refs/remotes/origin/*</code></li>
  180. * <li><code>+refs/heads/master</code></li>
  181. * <li><code>+refs/heads/master:refs/remotes/origin/master</code></li>
  182. * <li><code>+refs/heads/*:refs/remotes/origin/*</code></li>
  183. * <li><code>+refs/pull/&#42;/head:refs/remotes/origin/pr/*</code></li>
  184. * <li><code>:refs/heads/master</code></li>
  185. * </ul>
  186. *
  187. * @param spec
  188. * string describing the specification.
  189. * @throws IllegalArgumentException
  190. * the specification is invalid.
  191. */
  192. public RefSpec(final String spec) {
  193. this(spec, WildcardMode.REQUIRE_MATCH);
  194. }
  195. private RefSpec(final RefSpec p) {
  196. force = p.isForceUpdate();
  197. wildcard = p.isWildcard();
  198. srcName = p.getSource();
  199. dstName = p.getDestination();
  200. allowMismatchedWildcards = p.allowMismatchedWildcards;
  201. }
  202. /**
  203. * Check if this specification wants to forcefully update the destination.
  204. *
  205. * @return true if this specification asks for updates without merge tests.
  206. */
  207. public boolean isForceUpdate() {
  208. return force;
  209. }
  210. /**
  211. * Create a new RefSpec with a different force update setting.
  212. *
  213. * @param forceUpdate
  214. * new value for force update in the returned instance.
  215. * @return a new RefSpec with force update as specified.
  216. */
  217. public RefSpec setForceUpdate(final boolean forceUpdate) {
  218. final RefSpec r = new RefSpec(this);
  219. r.force = forceUpdate;
  220. return r;
  221. }
  222. /**
  223. * Check if this specification is actually a wildcard pattern.
  224. * <p>
  225. * If this is a wildcard pattern then the source and destination names
  226. * returned by {@link #getSource()} and {@link #getDestination()} will not
  227. * be actual ref names, but instead will be patterns.
  228. *
  229. * @return true if this specification could match more than one ref.
  230. */
  231. public boolean isWildcard() {
  232. return wildcard;
  233. }
  234. /**
  235. * Get the source ref description.
  236. * <p>
  237. * During a fetch this is the name of the ref on the remote repository we
  238. * are fetching from. During a push this is the name of the ref on the local
  239. * repository we are pushing out from.
  240. *
  241. * @return name (or wildcard pattern) to match the source ref.
  242. */
  243. public String getSource() {
  244. return srcName;
  245. }
  246. /**
  247. * Create a new RefSpec with a different source name setting.
  248. *
  249. * @param source
  250. * new value for source in the returned instance.
  251. * @return a new RefSpec with source as specified.
  252. * @throws IllegalStateException
  253. * There is already a destination configured, and the wildcard
  254. * status of the existing destination disagrees with the
  255. * wildcard status of the new source.
  256. */
  257. public RefSpec setSource(final String source) {
  258. final RefSpec r = new RefSpec(this);
  259. r.srcName = checkValid(source);
  260. if (isWildcard(r.srcName) && r.dstName == null)
  261. throw new IllegalStateException(JGitText.get().destinationIsNotAWildcard);
  262. if (isWildcard(r.srcName) != isWildcard(r.dstName))
  263. throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
  264. return r;
  265. }
  266. /**
  267. * Get the destination ref description.
  268. * <p>
  269. * During a fetch this is the local tracking branch that will be updated
  270. * with the new ObjectId after fetching is complete. During a push this is
  271. * the remote ref that will be updated by the remote's receive-pack process.
  272. * <p>
  273. * If null during a fetch no tracking branch should be updated and the
  274. * ObjectId should be stored transiently in order to prepare a merge.
  275. * <p>
  276. * If null during a push, use {@link #getSource()} instead.
  277. *
  278. * @return name (or wildcard) pattern to match the destination ref.
  279. */
  280. public String getDestination() {
  281. return dstName;
  282. }
  283. /**
  284. * Create a new RefSpec with a different destination name setting.
  285. *
  286. * @param destination
  287. * new value for destination in the returned instance.
  288. * @return a new RefSpec with destination as specified.
  289. * @throws IllegalStateException
  290. * There is already a source configured, and the wildcard status
  291. * of the existing source disagrees with the wildcard status of
  292. * the new destination.
  293. */
  294. public RefSpec setDestination(final String destination) {
  295. final RefSpec r = new RefSpec(this);
  296. r.dstName = checkValid(destination);
  297. if (isWildcard(r.dstName) && r.srcName == null)
  298. throw new IllegalStateException(JGitText.get().sourceIsNotAWildcard);
  299. if (isWildcard(r.srcName) != isWildcard(r.dstName))
  300. throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
  301. return r;
  302. }
  303. /**
  304. * Create a new RefSpec with a different source/destination name setting.
  305. *
  306. * @param source
  307. * new value for source in the returned instance.
  308. * @param destination
  309. * new value for destination in the returned instance.
  310. * @return a new RefSpec with destination as specified.
  311. * @throws IllegalArgumentException
  312. * The wildcard status of the new source disagrees with the
  313. * wildcard status of the new destination.
  314. */
  315. public RefSpec setSourceDestination(final String source,
  316. final String destination) {
  317. if (isWildcard(source) != isWildcard(destination))
  318. throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
  319. final RefSpec r = new RefSpec(this);
  320. r.wildcard = isWildcard(source);
  321. r.srcName = source;
  322. r.dstName = destination;
  323. return r;
  324. }
  325. /**
  326. * Does this specification's source description match the ref name?
  327. *
  328. * @param r
  329. * ref name that should be tested.
  330. * @return true if the names match; false otherwise.
  331. */
  332. public boolean matchSource(final String r) {
  333. return match(r, getSource());
  334. }
  335. /**
  336. * Does this specification's source description match the ref?
  337. *
  338. * @param r
  339. * ref whose name should be tested.
  340. * @return true if the names match; false otherwise.
  341. */
  342. public boolean matchSource(final Ref r) {
  343. return match(r.getName(), getSource());
  344. }
  345. /**
  346. * Does this specification's destination description match the ref name?
  347. *
  348. * @param r
  349. * ref name that should be tested.
  350. * @return true if the names match; false otherwise.
  351. */
  352. public boolean matchDestination(final String r) {
  353. return match(r, getDestination());
  354. }
  355. /**
  356. * Does this specification's destination description match the ref?
  357. *
  358. * @param r
  359. * ref whose name should be tested.
  360. * @return true if the names match; false otherwise.
  361. */
  362. public boolean matchDestination(final Ref r) {
  363. return match(r.getName(), getDestination());
  364. }
  365. /**
  366. * Expand this specification to exactly match a ref name.
  367. * <p>
  368. * Callers must first verify the passed ref name matches this specification,
  369. * otherwise expansion results may be unpredictable.
  370. *
  371. * @param r
  372. * a ref name that matched our source specification. Could be a
  373. * wildcard also.
  374. * @return a new specification expanded from provided ref name. Result
  375. * specification is wildcard if and only if provided ref name is
  376. * wildcard.
  377. * @throws IllegalStateException
  378. * when the RefSpec was constructed with wildcard mode that
  379. * doesn't require matching wildcards.
  380. */
  381. public RefSpec expandFromSource(final String r) {
  382. if (allowMismatchedWildcards != WildcardMode.REQUIRE_MATCH) {
  383. throw new IllegalStateException(
  384. JGitText.get().invalidExpandWildcard);
  385. }
  386. return isWildcard() ? new RefSpec(this).expandFromSourceImp(r) : this;
  387. }
  388. private RefSpec expandFromSourceImp(final String name) {
  389. final String psrc = srcName, pdst = dstName;
  390. wildcard = false;
  391. srcName = name;
  392. dstName = expandWildcard(name, psrc, pdst);
  393. return this;
  394. }
  395. /**
  396. * Expand this specification to exactly match a ref.
  397. * <p>
  398. * Callers must first verify the passed ref matches this specification,
  399. * otherwise expansion results may be unpredictable.
  400. *
  401. * @param r
  402. * a ref that matched our source specification. Could be a
  403. * wildcard also.
  404. * @return a new specification expanded from provided ref name. Result
  405. * specification is wildcard if and only if provided ref name is
  406. * wildcard.
  407. * @throws IllegalStateException
  408. * when the RefSpec was constructed with wildcard mode that
  409. * doesn't require matching wildcards.
  410. */
  411. public RefSpec expandFromSource(final Ref r) {
  412. return expandFromSource(r.getName());
  413. }
  414. /**
  415. * Expand this specification to exactly match a ref name.
  416. * <p>
  417. * Callers must first verify the passed ref name matches this specification,
  418. * otherwise expansion results may be unpredictable.
  419. *
  420. * @param r
  421. * a ref name that matched our destination specification. Could
  422. * be a wildcard also.
  423. * @return a new specification expanded from provided ref name. Result
  424. * specification is wildcard if and only if provided ref name is
  425. * wildcard.
  426. * @throws IllegalStateException
  427. * when the RefSpec was constructed with wildcard mode that
  428. * doesn't require matching wildcards.
  429. */
  430. public RefSpec expandFromDestination(final String r) {
  431. if (allowMismatchedWildcards != WildcardMode.REQUIRE_MATCH) {
  432. throw new IllegalStateException(
  433. JGitText.get().invalidExpandWildcard);
  434. }
  435. return isWildcard() ? new RefSpec(this).expandFromDstImp(r) : this;
  436. }
  437. private RefSpec expandFromDstImp(final String name) {
  438. final String psrc = srcName, pdst = dstName;
  439. wildcard = false;
  440. srcName = expandWildcard(name, pdst, psrc);
  441. dstName = name;
  442. return this;
  443. }
  444. /**
  445. * Expand this specification to exactly match a ref.
  446. * <p>
  447. * Callers must first verify the passed ref matches this specification,
  448. * otherwise expansion results may be unpredictable.
  449. *
  450. * @param r
  451. * a ref that matched our destination specification.
  452. * @return a new specification expanded from provided ref name. Result
  453. * specification is wildcard if and only if provided ref name is
  454. * wildcard.
  455. * @throws IllegalStateException
  456. * when the RefSpec was constructed with wildcard mode that
  457. * doesn't require matching wildcards.
  458. */
  459. public RefSpec expandFromDestination(final Ref r) {
  460. return expandFromDestination(r.getName());
  461. }
  462. private boolean match(final String name, final String s) {
  463. if (s == null)
  464. return false;
  465. if (isWildcard(s)) {
  466. int wildcardIndex = s.indexOf('*');
  467. String prefix = s.substring(0, wildcardIndex);
  468. String suffix = s.substring(wildcardIndex + 1);
  469. return name.length() > prefix.length() + suffix.length()
  470. && name.startsWith(prefix) && name.endsWith(suffix);
  471. }
  472. return name.equals(s);
  473. }
  474. private static String expandWildcard(String name, String patternA,
  475. String patternB) {
  476. int a = patternA.indexOf('*');
  477. int trailingA = patternA.length() - (a + 1);
  478. int b = patternB.indexOf('*');
  479. String match = name.substring(a, name.length() - trailingA);
  480. return patternB.substring(0, b) + match + patternB.substring(b + 1);
  481. }
  482. private static String checkValid(String spec) {
  483. if (spec != null && !isValid(spec))
  484. throw new IllegalArgumentException(MessageFormat.format(
  485. JGitText.get().invalidRefSpec, spec));
  486. return spec;
  487. }
  488. private static boolean isValid(final String s) {
  489. if (s.startsWith("/")) //$NON-NLS-1$
  490. return false;
  491. if (s.contains("//")) //$NON-NLS-1$
  492. return false;
  493. if (s.endsWith("/")) //$NON-NLS-1$
  494. return false;
  495. int i = s.indexOf('*');
  496. if (i != -1) {
  497. if (s.indexOf('*', i + 1) > i)
  498. return false;
  499. }
  500. return true;
  501. }
  502. public int hashCode() {
  503. int hc = 0;
  504. if (getSource() != null)
  505. hc = hc * 31 + getSource().hashCode();
  506. if (getDestination() != null)
  507. hc = hc * 31 + getDestination().hashCode();
  508. return hc;
  509. }
  510. public boolean equals(final Object obj) {
  511. if (!(obj instanceof RefSpec))
  512. return false;
  513. final RefSpec b = (RefSpec) obj;
  514. if (isForceUpdate() != b.isForceUpdate())
  515. return false;
  516. if (isWildcard() != b.isWildcard())
  517. return false;
  518. if (!eq(getSource(), b.getSource()))
  519. return false;
  520. if (!eq(getDestination(), b.getDestination()))
  521. return false;
  522. return true;
  523. }
  524. private static boolean eq(final String a, final String b) {
  525. if (a == b)
  526. return true;
  527. if (a == null || b == null)
  528. return false;
  529. return a.equals(b);
  530. }
  531. public String toString() {
  532. final StringBuilder r = new StringBuilder();
  533. if (isForceUpdate())
  534. r.append('+');
  535. if (getSource() != null)
  536. r.append(getSource());
  537. if (getDestination() != null) {
  538. r.append(':');
  539. r.append(getDestination());
  540. }
  541. return r.toString();
  542. }
  543. }