View Javadoc
1   /*
2    * Copyright (C) 2016, Google Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.internal.ketch;
12  
13  import static java.util.concurrent.TimeUnit.MILLISECONDS;
14  import static java.util.concurrent.TimeUnit.SECONDS;
15  import static org.eclipse.jgit.internal.ketch.Proposal.State.EXECUTED;
16  import static org.eclipse.jgit.internal.ketch.Proposal.State.QUEUED;
17  import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
18  import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
19  
20  import java.io.IOException;
21  import java.util.Collection;
22  
23  import org.eclipse.jgit.internal.JGitText;
24  import org.eclipse.jgit.transport.PreReceiveHook;
25  import org.eclipse.jgit.transport.ProgressSpinner;
26  import org.eclipse.jgit.transport.ReceiveCommand;
27  import org.eclipse.jgit.transport.ReceivePack;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  /**
32   * PreReceiveHook for handling push traffic in a Ketch system.
33   * <p>
34   * Install an instance on {@link org.eclipse.jgit.transport.ReceivePack} to
35   * capture the commands and other connection state and relay them through the
36   * {@link org.eclipse.jgit.internal.ketch.KetchLeader}, allowing the leader to
37   * gain consensus about the new reference state.
38   */
39  public class KetchPreReceive implements PreReceiveHook {
40  	private static final Logger log = LoggerFactory.getLogger(KetchPreReceive.class);
41  
42  	private final KetchLeader leader;
43  
44  	/**
45  	 * Construct a hook executing updates through a
46  	 * {@link org.eclipse.jgit.internal.ketch.KetchLeader}.
47  	 *
48  	 * @param leader
49  	 *            leader for this repository.
50  	 */
51  	public KetchPreReceive(KetchLeader leader) {
52  		this.leader = leader;
53  	}
54  
55  	/** {@inheritDoc} */
56  	@Override
57  	public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> cmds) {
58  		cmds = ReceiveCommand.filter(cmds, NOT_ATTEMPTED);
59  		if (cmds.isEmpty()) {
60  			return;
61  		}
62  
63  		try {
64  			Proposal proposal = new Proposal(rp.getRevWalk(), cmds)
65  				.setPushCertificate(rp.getPushCertificate())
66  				.setAuthor(rp.getRefLogIdent())
67  				.setMessage("push"); //$NON-NLS-1$
68  			leader.queueProposal(proposal);
69  			if (proposal.isDone()) {
70  				// This failed fast, e.g. conflict or bad precondition.
71  				return;
72  			}
73  
74  			ProgressSpinner spinner = new ProgressSpinner(
75  					rp.getMessageOutputStream());
76  			if (proposal.getState() == QUEUED) {
77  				waitForQueue(proposal, spinner);
78  			}
79  			if (!proposal.isDone()) {
80  				waitForPropose(proposal, spinner);
81  			}
82  		} catch (IOException | InterruptedException e) {
83  			String msg = JGitText.get().transactionAborted;
84  			for (ReceiveCommand cmd : cmds) {
85  				if (cmd.getResult() == NOT_ATTEMPTED) {
86  					cmd.setResult(REJECTED_OTHER_REASON, msg);
87  				}
88  			}
89  			log.error(msg, e);
90  		}
91  	}
92  
93  	private void waitForQueue(Proposal proposal, ProgressSpinner spinner)
94  			throws InterruptedException {
95  		spinner.beginTask(KetchText.get().waitingForQueue, 1, SECONDS);
96  		while (!proposal.awaitStateChange(QUEUED, 250, MILLISECONDS)) {
97  			spinner.update();
98  		}
99  		switch (proposal.getState()) {
100 		case RUNNING:
101 		default:
102 			spinner.endTask(KetchText.get().starting);
103 			break;
104 
105 		case EXECUTED:
106 			spinner.endTask(KetchText.get().accepted);
107 			break;
108 
109 		case ABORTED:
110 			spinner.endTask(KetchText.get().failed);
111 			break;
112 		}
113 	}
114 
115 	private void waitForPropose(Proposal proposal, ProgressSpinner spinner)
116 			throws InterruptedException {
117 		spinner.beginTask(KetchText.get().proposingUpdates, 2, SECONDS);
118 		while (!proposal.await(250, MILLISECONDS)) {
119 			spinner.update();
120 		}
121 		spinner.endTask(proposal.getState() == EXECUTED
122 				? KetchText.get().accepted
123 				: KetchText.get().failed);
124 	}
125 }