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  			try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) {
92  				parentTreeId = fmt.idFor(OBJ_TREE, new byte[] {});
93  			}
94  			tree = RefTree.newEmptyTree();
95  		}
96  	}
97  
98  	@Nullable
99  	Ref exactRef(ObjectReader reader, String name) throws IOException {
100 		return tree.exactRef(reader, name);
101 	}
102 
103 	/**
104 	 * Execute an update from {@link RefTreeUpdate} or {@link RefTreeRename}.
105 	 *
106 	 * @param rw
107 	 *            current RevWalk handling the update or rename.
108 	 * @param todo
109 	 *            commands to execute. Must never be a bootstrap reference name.
110 	 * @throws IOException
111 	 *             the storage system is unable to read or write data.
112 	 */
113 	void execute(RevWalk rw, List<Command> todo) throws IOException {
114 		for (Command c : todo) {
115 			if (c.getResult() != NOT_ATTEMPTED) {
116 				Command.abort(todo, null);
117 				return;
118 			}
119 			if (refdb.conflictsWithBootstrap(c.getRefName())) {
120 				c.setResult(REJECTED_OTHER_REASON, MessageFormat
121 						.format(JGitText.get().invalidRefName, c.getRefName()));
122 				Command.abort(todo, null);
123 				return;
124 			}
125 		}
126 
127 		if (apply(todo) && newCommitId != null) {
128 			commit(rw, todo);
129 		}
130 	}
131 
132 	private boolean apply(List<Command> todo) throws IOException {
133 		if (!tree.apply(todo)) {
134 			// apply set rejection information on commands.
135 			return false;
136 		}
137 
138 		Repository repo = refdb.getRepository();
139 		try (ObjectInserter ins = repo.newObjectInserter()) {
140 			CommitBuilder b = new CommitBuilder();
141 			b.setTreeId(tree.writeTree(ins));
142 			if (parentTreeId.equals(b.getTreeId())) {
143 				for (Command c : todo) {
144 					c.setResult(OK);
145 				}
146 				return true;
147 			}
148 			if (!parentCommitId.equals(ObjectId.zeroId())) {
149 				b.setParentId(parentCommitId);
150 			}
151 
152 			author = getRefLogIdent();
153 			if (author == null) {
154 				author = new PersonIdent(repo);
155 			}
156 			b.setAuthor(author);
157 			b.setCommitter(author);
158 			b.setMessage(getRefLogMessage());
159 			newCommitId = ins.insert(b);
160 			ins.flush();
161 		}
162 		return true;
163 	}
164 
165 	private void commit(RevWalk rw, List<Command> todo) throws IOException {
166 		ReceiveCommand commit = new ReceiveCommand(
167 				parentCommitId, newCommitId,
168 				refdb.getTxnCommitted());
169 		updateBootstrap(rw, commit);
170 
171 		if (commit.getResult() == OK) {
172 			for (Command c : todo) {
173 				c.setResult(OK);
174 			}
175 		} else {
176 			Command.abort(todo, commit.getResult().name());
177 		}
178 	}
179 
180 	private void updateBootstrap(RevWalk rw, ReceiveCommand commit)
181 			throws IOException {
182 		BatchRefUpdate u = refdb.getBootstrap().newBatchUpdate();
183 		u.setAllowNonFastForwards(true);
184 		u.setPushCertificate(getPushCertificate());
185 		if (isRefLogDisabled()) {
186 			u.disableRefLog();
187 		} else {
188 			u.setRefLogIdent(author);
189 			u.setRefLogMessage(getRefLogMessage(), false);
190 		}
191 		u.addCommand(commit);
192 		u.execute(rw, NullProgressMonitor.INSTANCE);
193 	}
194 }