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 org.eclipse.jgit.internal.ketch.KetchReplica.State.AHEAD;
14  import static org.eclipse.jgit.internal.ketch.KetchReplica.State.DIVERGENT;
15  import static org.eclipse.jgit.internal.ketch.KetchReplica.State.LAGGING;
16  import static org.eclipse.jgit.internal.ketch.KetchReplica.State.UNKNOWN;
17  import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
18  
19  import java.io.IOException;
20  import java.util.Collections;
21  import java.util.Map;
22  
23  import org.eclipse.jgit.errors.MissingObjectException;
24  import org.eclipse.jgit.lib.AnyObjectId;
25  import org.eclipse.jgit.lib.ObjectId;
26  import org.eclipse.jgit.lib.Ref;
27  import org.eclipse.jgit.lib.Repository;
28  import org.eclipse.jgit.revwalk.RevCommit;
29  import org.eclipse.jgit.revwalk.RevWalk;
30  import org.eclipse.jgit.transport.ReceiveCommand;
31  
32  /**
33   * A helper to check if a {@link KetchReplica} is ahead or behind the leader.
34   */
35  class LagCheck implements AutoCloseable {
36  	private final KetchReplica replica;
37  	private final Repository repo;
38  	private RevWalk rw;
39  	private ObjectId remoteId;
40  
41  	LagCheck(KetchReplica replica, Repository repo) {
42  		this.replica = replica;
43  		this.repo = repo;
44  		initRevWalk();
45  	}
46  
47  	private void initRevWalk() {
48  		if (rw != null) {
49  			rw.close();
50  		}
51  
52  		rw = new RevWalk(repo);
53  		rw.setRetainBody(false);
54  	}
55  
56  	/** {@inheritDoc} */
57  	@Override
58  	public void close() {
59  		if (rw != null) {
60  			rw.close();
61  			rw = null;
62  		}
63  	}
64  
65  	ObjectId getRemoteId() {
66  		return remoteId;
67  	}
68  
69  	KetchReplica.State check(ObjectId acceptId, ReceiveCommand acceptCmd) {
70  		remoteId = acceptId;
71  		if (remoteId == null) {
72  			// Nothing advertised by the replica, value is unknown.
73  			return UNKNOWN;
74  		}
75  
76  		if (AnyObjectId.isEqual(remoteId, ObjectId.zeroId())) {
77  			// Replica does not have the txnAccepted reference.
78  			return LAGGING;
79  		}
80  
81  		try {
82  			RevCommit remote;
83  			try {
84  				remote = parseRemoteCommit(acceptCmd.getRefName());
85  			} catch (RefGoneException gone) {
86  				// Replica does not have the txnAccepted reference.
87  				return LAGGING;
88  			} catch (MissingObjectException notFound) {
89  				// Local repository does not know this commit so it cannot
90  				// be including the replica's log.
91  				return DIVERGENT;
92  			}
93  
94  			RevCommit head = rw.parseCommit(acceptCmd.getNewId());
95  			if (rw.isMergedInto(remote, head)) {
96  				return LAGGING;
97  			}
98  
99  			// TODO(sop) Check term to see if my leader was deposed.
100 			if (rw.isMergedInto(head, remote)) {
101 				return AHEAD;
102 			}
103 			return DIVERGENT;
104 		} catch (IOException err) {
105 			KetchReplica.log.error(String.format(
106 					"Cannot compare %s", //$NON-NLS-1$
107 					acceptCmd.getRefName()), err);
108 			return UNKNOWN;
109 		}
110 	}
111 
112 	private RevCommit parseRemoteCommit(String refName)
113 			throws IOException, MissingObjectException, RefGoneException {
114 		try {
115 			return rw.parseCommit(remoteId);
116 		} catch (MissingObjectException notLocal) {
117 			// Fall through and try to acquire the object by fetching it.
118 		}
119 
120 		ReplicaFetchRequest fetch = new ReplicaFetchRequest(
121 				Collections.singleton(refName),
122 				Collections.<ObjectId> emptySet());
123 		try {
124 			replica.blockingFetch(repo, fetch);
125 		} catch (IOException fetchErr) {
126 			KetchReplica.log.error(String.format(
127 					"Cannot fetch %s (%s) from %s", //$NON-NLS-1$
128 					remoteId.abbreviate(8).name(), refName,
129 					replica.describeForLog()), fetchErr);
130 			throw new MissingObjectException(remoteId, OBJ_COMMIT);
131 		}
132 
133 		Map<String, Ref> adv = fetch.getRefs();
134 		if (adv == null) {
135 			throw new MissingObjectException(remoteId, OBJ_COMMIT);
136 		}
137 
138 		Ref ref = adv.get(refName);
139 		if (ref == null || ref.getObjectId() == null) {
140 			throw new RefGoneException();
141 		}
142 
143 		initRevWalk();
144 		remoteId = ref.getObjectId();
145 		return rw.parseCommit(remoteId);
146 	}
147 
148 	private static class RefGoneException extends Exception {
149 		private static final long serialVersionUID = 1L;
150 	}
151 }