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.storage.reftree;
12  
13  import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
14  import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
15  import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
16  import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
17  import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
18  import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
19  import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
20  
21  import java.io.IOException;
22  import java.text.MessageFormat;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import org.eclipse.jgit.annotations.Nullable;
27  import org.eclipse.jgit.internal.JGitText;
28  import org.eclipse.jgit.lib.BatchRefUpdate;
29  import org.eclipse.jgit.lib.CommitBuilder;
30  import org.eclipse.jgit.lib.NullProgressMonitor;
31  import org.eclipse.jgit.lib.ObjectId;
32  import org.eclipse.jgit.lib.ObjectInserter;
33  import org.eclipse.jgit.lib.ObjectReader;
34  import org.eclipse.jgit.lib.PersonIdent;
35  import org.eclipse.jgit.lib.ProgressMonitor;
36  import org.eclipse.jgit.lib.Ref;
37  import org.eclipse.jgit.lib.Repository;
38  import org.eclipse.jgit.revwalk.RevCommit;
39  import org.eclipse.jgit.revwalk.RevWalk;
40  import org.eclipse.jgit.transport.ReceiveCommand;
41  
42  /** Batch update a {@link RefTreeDatabase}. */
43  class RefTreeBatch extends BatchRefUpdate {
44  	private final RefTreeDatabase refdb;
45  	private Ref src;
46  	private ObjectId parentCommitId;
47  	private ObjectId parentTreeId;
48  	private RefTree tree;
49  	private PersonIdent author;
50  	private ObjectId newCommitId;
51  
52  	RefTreeBatch(RefTreeDatabase refdb) {
53  		super(refdb);
54  		this.refdb = refdb;
55  	}
56  
57  	/** {@inheritDoc} */
58  	@Override
59  	public void execute(RevWalk rw, ProgressMonitor monitor)
60  			throws IOException {
61  		List<Command> todo = new ArrayList<>(getCommands().size());
62  		for (ReceiveCommand c : getCommands()) {
63  			if (!isAllowNonFastForwards()) {
64  				if (c.getType() == UPDATE) {
65  					c.updateType(rw);
66  				}
67  				if (c.getType() == UPDATE_NONFASTFORWARD) {
68  					c.setResult(REJECTED_NONFASTFORWARD);
69  					if (isAtomic()) {
70  						ReceiveCommand.abort(getCommands());
71  						return;
72  					}
73  					continue;
74  				}
75  			}
76  			todo.add(new Command(rw, c));
77  		}
78  		init(rw);
79  		execute(rw, todo);
80  	}
81  
82  	void init(RevWalk rw) throws IOException {
83  		src = refdb.getBootstrap().exactRef(refdb.getTxnCommitted());
84  		if (src != null && src.getObjectId() != null) {
85  			RevCommit c = rw.parseCommit(src.getObjectId());
86  			parentCommitId = c;
87  			parentTreeId = c.getTree();
88  			tree = RefTree.read(rw.getObjectReader(), c.getTree());
89  		} else {
90  			parentCommitId = ObjectId.zeroId();
91  			parentTreeId = new ObjectInserter.Formatter()
92  					.idFor(OBJ_TREE, new byte[] {});
93  			tree = RefTree.newEmptyTree();
94  		}
95  	}
96  
97  	@Nullable
98  	Ref exactRef(ObjectReader reader, String name) throws IOException {
99  		return tree.exactRef(reader, name);
100 	}
101 
102 	/**
103 	 * Execute an update from {@link RefTreeUpdate} or {@link RefTreeRename}.
104 	 *
105 	 * @param rw
106 	 *            current RevWalk handling the update or rename.
107 	 * @param todo
108 	 *            commands to execute. Must never be a bootstrap reference name.
109 	 * @throws IOException
110 	 *             the storage system is unable to read or write data.
111 	 */
112 	void execute(RevWalk rw, List<Command> todo) throws IOException {
113 		for (Command c : todo) {
114 			if (c.getResult() != NOT_ATTEMPTED) {
115 				Command.abort(todo, null);
116 				return;
117 			}
118 			if (refdb.conflictsWithBootstrap(c.getRefName())) {
119 				c.setResult(REJECTED_OTHER_REASON, MessageFormat
120 						.format(JGitText.get().invalidRefName, c.getRefName()));
121 				Command.abort(todo, null);
122 				return;
123 			}
124 		}
125 
126 		if (apply(todo) && newCommitId != null) {
127 			commit(rw, todo);
128 		}
129 	}
130 
131 	private boolean apply(List<Command> todo) throws IOException {
132 		if (!tree.apply(todo)) {
133 			// apply set rejection information on commands.
134 			return false;
135 		}
136 
137 		Repository repo = refdb.getRepository();
138 		try (ObjectInserter ins = repo.newObjectInserter()) {
139 			CommitBuilder b = new CommitBuilder();
140 			b.setTreeId(tree.writeTree(ins));
141 			if (parentTreeId.equals(b.getTreeId())) {
142 				for (Command c : todo) {
143 					c.setResult(OK);
144 				}
145 				return true;
146 			}
147 			if (!parentCommitId.equals(ObjectId.zeroId())) {
148 				b.setParentId(parentCommitId);
149 			}
150 
151 			author = getRefLogIdent();
152 			if (author == null) {
153 				author = new PersonIdent(repo);
154 			}
155 			b.setAuthor(author);
156 			b.setCommitter(author);
157 			b.setMessage(getRefLogMessage());
158 			newCommitId = ins.insert(b);
159 			ins.flush();
160 		}
161 		return true;
162 	}
163 
164 	private void commit(RevWalk rw, List<Command> todo) throws IOException {
165 		ReceiveCommand commit = new ReceiveCommand(
166 				parentCommitId, newCommitId,
167 				refdb.getTxnCommitted());
168 		updateBootstrap(rw, commit);
169 
170 		if (commit.getResult() == OK) {
171 			for (Command c : todo) {
172 				c.setResult(OK);
173 			}
174 		} else {
175 			Command.abort(todo, commit.getResult().name());
176 		}
177 	}
178 
179 	private void updateBootstrap(RevWalk rw, ReceiveCommand commit)
180 			throws IOException {
181 		BatchRefUpdate u = refdb.getBootstrap().newBatchUpdate();
182 		u.setAllowNonFastForwards(true);
183 		u.setPushCertificate(getPushCertificate());
184 		if (isRefLogDisabled()) {
185 			u.disableRefLog();
186 		} else {
187 			u.setRefLogIdent(author);
188 			u.setRefLogMessage(getRefLogMessage(), false);
189 		}
190 		u.addCommand(commit);
191 		u.execute(rw, NullProgressMonitor.INSTANCE);
192 	}
193 }