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.

Command.java 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. /*
  2. * Copyright (C) 2016, 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. package org.eclipse.jgit.internal.storage.reftree;
  44. import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
  45. import static org.eclipse.jgit.lib.Constants.encode;
  46. import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
  47. import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK;
  48. import static org.eclipse.jgit.lib.Ref.Storage.NETWORK;
  49. import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
  50. import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
  51. import java.io.IOException;
  52. import org.eclipse.jgit.annotations.Nullable;
  53. import org.eclipse.jgit.dircache.DirCacheEntry;
  54. import org.eclipse.jgit.errors.MissingObjectException;
  55. import org.eclipse.jgit.internal.JGitText;
  56. import org.eclipse.jgit.lib.ObjectId;
  57. import org.eclipse.jgit.lib.ObjectIdRef;
  58. import org.eclipse.jgit.lib.ObjectInserter;
  59. import org.eclipse.jgit.lib.Ref;
  60. import org.eclipse.jgit.revwalk.RevObject;
  61. import org.eclipse.jgit.revwalk.RevTag;
  62. import org.eclipse.jgit.revwalk.RevWalk;
  63. import org.eclipse.jgit.transport.ReceiveCommand;
  64. import org.eclipse.jgit.transport.ReceiveCommand.Result;
  65. /**
  66. * Command to create, update or delete an entry inside a {@link RefTree}.
  67. * <p>
  68. * Unlike {@link ReceiveCommand} (which can only update a reference to an
  69. * {@link ObjectId}), a RefTree Command can also create, modify or delete
  70. * symbolic references to a target reference.
  71. * <p>
  72. * RefTree Commands may wrap a {@code ReceiveCommand} to allow callers to
  73. * process an existing ReceiveCommand against a RefTree.
  74. * <p>
  75. * Commands should be passed into {@link RefTree#apply(java.util.Collection)}
  76. * for processing.
  77. */
  78. public class Command {
  79. /**
  80. * Set unprocessed commands as failed due to transaction aborted.
  81. * <p>
  82. * If a command is still {@link Result#NOT_ATTEMPTED} it will be set to
  83. * {@link Result#REJECTED_OTHER_REASON}. If {@code why} is non-null its
  84. * contents will be used as the message for the first command status.
  85. *
  86. * @param commands
  87. * commands to mark as failed.
  88. * @param why
  89. * optional message to set on the first aborted command.
  90. */
  91. public static void abort(Iterable<Command> commands, @Nullable String why) {
  92. if (why == null || why.isEmpty()) {
  93. why = JGitText.get().transactionAborted;
  94. }
  95. for (Command c : commands) {
  96. if (c.getResult() == NOT_ATTEMPTED) {
  97. c.setResult(REJECTED_OTHER_REASON, why);
  98. why = JGitText.get().transactionAborted;
  99. }
  100. }
  101. }
  102. private final Ref oldRef;
  103. private final Ref newRef;
  104. private final ReceiveCommand cmd;
  105. private Result result;
  106. /**
  107. * Create a command to create, update or delete a reference.
  108. * <p>
  109. * At least one of {@code oldRef} or {@code newRef} must be supplied.
  110. *
  111. * @param oldRef
  112. * expected value. Null if the ref should not exist.
  113. * @param newRef
  114. * desired value, must be peeled if not null and not symbolic.
  115. * Null to delete the ref.
  116. */
  117. public Command(@Nullable Ref oldRef, @Nullable Ref newRef) {
  118. this.oldRef = oldRef;
  119. this.newRef = newRef;
  120. this.cmd = null;
  121. this.result = NOT_ATTEMPTED;
  122. if (oldRef == null && newRef == null) {
  123. throw new IllegalArgumentException();
  124. }
  125. if (newRef != null && !newRef.isPeeled() && !newRef.isSymbolic()) {
  126. throw new IllegalArgumentException();
  127. }
  128. if (oldRef != null && newRef != null
  129. && !oldRef.getName().equals(newRef.getName())) {
  130. throw new IllegalArgumentException();
  131. }
  132. }
  133. /**
  134. * Construct a RefTree command wrapped around a ReceiveCommand.
  135. *
  136. * @param rw
  137. * walk instance to peel the {@code newId}.
  138. * @param cmd
  139. * command received from a push client.
  140. * @throws MissingObjectException
  141. * {@code oldId} or {@code newId} is missing.
  142. * @throws IOException
  143. * {@code oldId} or {@code newId} cannot be peeled.
  144. */
  145. public Command(RevWalk rw, ReceiveCommand cmd)
  146. throws MissingObjectException, IOException {
  147. this.oldRef = toRef(rw, cmd.getOldId(), cmd.getRefName(), false);
  148. this.newRef = toRef(rw, cmd.getNewId(), cmd.getRefName(), true);
  149. this.cmd = cmd;
  150. }
  151. static Ref toRef(RevWalk rw, ObjectId id, String name,
  152. boolean mustExist) throws MissingObjectException, IOException {
  153. if (ObjectId.zeroId().equals(id)) {
  154. return null;
  155. }
  156. try {
  157. RevObject o = rw.parseAny(id);
  158. if (o instanceof RevTag) {
  159. RevObject p = rw.peel(o);
  160. return new ObjectIdRef.PeeledTag(NETWORK, name, id, p.copy());
  161. }
  162. return new ObjectIdRef.PeeledNonTag(NETWORK, name, id);
  163. } catch (MissingObjectException e) {
  164. if (mustExist) {
  165. throw e;
  166. }
  167. return new ObjectIdRef.Unpeeled(NETWORK, name, id);
  168. }
  169. }
  170. /** @return name of the reference affected by this command. */
  171. public String getRefName() {
  172. if (cmd != null) {
  173. return cmd.getRefName();
  174. } else if (newRef != null) {
  175. return newRef.getName();
  176. }
  177. return oldRef.getName();
  178. }
  179. /**
  180. * Set the result of this command.
  181. *
  182. * @param result
  183. * the command result.
  184. */
  185. public void setResult(Result result) {
  186. setResult(result, null);
  187. }
  188. /**
  189. * Set the result of this command.
  190. *
  191. * @param result
  192. * the command result.
  193. * @param why
  194. * optional message explaining the result status.
  195. */
  196. public void setResult(Result result, @Nullable String why) {
  197. if (cmd != null) {
  198. cmd.setResult(result, why);
  199. } else {
  200. this.result = result;
  201. }
  202. }
  203. /** @return result of executing this command. */
  204. public Result getResult() {
  205. return cmd != null ? cmd.getResult() : result;
  206. }
  207. /** @return optional message explaining command failure. */
  208. @Nullable
  209. public String getMessage() {
  210. return cmd != null ? cmd.getMessage() : null;
  211. }
  212. /**
  213. * Old peeled reference.
  214. *
  215. * @return the old reference; null if the command is creating the reference.
  216. */
  217. @Nullable
  218. public Ref getOldRef() {
  219. return oldRef;
  220. }
  221. /**
  222. * New peeled reference.
  223. *
  224. * @return the new reference; null if the command is deleting the reference.
  225. */
  226. @Nullable
  227. public Ref getNewRef() {
  228. return newRef;
  229. }
  230. @Override
  231. public String toString() {
  232. StringBuilder s = new StringBuilder();
  233. append(s, oldRef, "CREATE"); //$NON-NLS-1$
  234. s.append(' ');
  235. append(s, newRef, "DELETE"); //$NON-NLS-1$
  236. s.append(' ').append(getRefName());
  237. s.append(' ').append(getResult());
  238. if (getMessage() != null) {
  239. s.append(' ').append(getMessage());
  240. }
  241. return s.toString();
  242. }
  243. private static void append(StringBuilder s, Ref r, String nullName) {
  244. if (r == null) {
  245. s.append(nullName);
  246. } else if (r.isSymbolic()) {
  247. s.append(r.getTarget().getName());
  248. } else {
  249. ObjectId id = r.getObjectId();
  250. if (id != null) {
  251. s.append(id.name());
  252. }
  253. }
  254. }
  255. /**
  256. * Check the entry is consistent with either the old or the new ref.
  257. *
  258. * @param entry
  259. * current entry; null if the entry does not exist.
  260. * @return true if entry matches {@link #getOldRef()} or
  261. * {@link #getNewRef()}; otherwise false.
  262. */
  263. boolean checkRef(@Nullable DirCacheEntry entry) {
  264. if (entry != null && entry.getRawMode() == 0) {
  265. entry = null;
  266. }
  267. return check(entry, oldRef) || check(entry, newRef);
  268. }
  269. private static boolean check(@Nullable DirCacheEntry cur,
  270. @Nullable Ref exp) {
  271. if (cur == null) {
  272. // Does not exist, ok if oldRef does not exist.
  273. return exp == null;
  274. } else if (exp == null) {
  275. // Expected to not exist, but currently exists, fail.
  276. return false;
  277. }
  278. if (exp.isSymbolic()) {
  279. String dst = exp.getTarget().getName();
  280. return cur.getRawMode() == TYPE_SYMLINK
  281. && cur.getObjectId().equals(symref(dst));
  282. }
  283. return cur.getRawMode() == TYPE_GITLINK
  284. && cur.getObjectId().equals(exp.getObjectId());
  285. }
  286. static ObjectId symref(String s) {
  287. @SuppressWarnings("resource")
  288. ObjectInserter.Formatter fmt = new ObjectInserter.Formatter();
  289. return fmt.idFor(OBJ_BLOB, encode(s));
  290. }
  291. }