View Javadoc
1   /*
2    * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
3    * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
4    * and other copyright owners as documented in the project's IP log.
5    *
6    * This program and the accompanying materials are made available
7    * under the terms of the Eclipse Distribution License v1.0 which
8    * accompanies this distribution, is reproduced below, and is
9    * available at http://www.eclipse.org/org/documents/edl-v10.php
10   *
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or
14   * without modification, are permitted provided that the following
15   * conditions are met:
16   *
17   * - Redistributions of source code must retain the above copyright
18   *   notice, this list of conditions and the following disclaimer.
19   *
20   * - Redistributions in binary form must reproduce the above
21   *   copyright notice, this list of conditions and the following
22   *   disclaimer in the documentation and/or other materials provided
23   *   with the distribution.
24   *
25   * - Neither the name of the Eclipse Foundation, Inc. nor the
26   *   names of its contributors may be used to endorse or promote
27   *   products derived from this software without specific prior
28   *   written permission.
29   *
30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43   */
44  package org.eclipse.jgit.api;
45  
46  import java.io.IOException;
47  import java.text.MessageFormat;
48  
49  import org.eclipse.jgit.api.RebaseCommand.Operation;
50  import org.eclipse.jgit.api.errors.CanceledException;
51  import org.eclipse.jgit.api.errors.DetachedHeadException;
52  import org.eclipse.jgit.api.errors.GitAPIException;
53  import org.eclipse.jgit.api.errors.InvalidConfigurationException;
54  import org.eclipse.jgit.api.errors.InvalidRemoteException;
55  import org.eclipse.jgit.api.errors.JGitInternalException;
56  import org.eclipse.jgit.api.errors.NoHeadException;
57  import org.eclipse.jgit.api.errors.RefNotAdvertisedException;
58  import org.eclipse.jgit.api.errors.RefNotFoundException;
59  import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
60  import org.eclipse.jgit.internal.JGitText;
61  import org.eclipse.jgit.lib.AnyObjectId;
62  import org.eclipse.jgit.lib.Config;
63  import org.eclipse.jgit.lib.ConfigConstants;
64  import org.eclipse.jgit.lib.Constants;
65  import org.eclipse.jgit.lib.NullProgressMonitor;
66  import org.eclipse.jgit.lib.ProgressMonitor;
67  import org.eclipse.jgit.lib.Ref;
68  import org.eclipse.jgit.lib.Repository;
69  import org.eclipse.jgit.lib.RepositoryState;
70  import org.eclipse.jgit.merge.MergeStrategy;
71  import org.eclipse.jgit.transport.FetchResult;
72  
73  /**
74   * The Pull command
75   *
76   * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-pull.html"
77   *      >Git documentation about Pull</a>
78   */
79  public class PullCommand extends TransportCommand<PullCommand, PullResult> {
80  
81  	private final static String DOT = "."; //$NON-NLS-1$
82  
83  	private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
84  
85  	private PullRebaseMode pullRebaseMode = null;
86  
87  	private String remote;
88  
89  	private String remoteBranchName;
90  
91  	private MergeStrategy strategy = MergeStrategy.RECURSIVE;
92  
93  	private enum PullRebaseMode implements Config.ConfigEnum {
94  		REBASE_PRESERVE("preserve", true, true), //$NON-NLS-1$
95  		REBASE("true", true, false), //$NON-NLS-1$
96  		NO_REBASE("false", false, false); //$NON-NLS-1$
97  
98  		private final String configValue;
99  
100 		private final boolean rebase;
101 
102 		private final boolean preserveMerges;
103 
104 		PullRebaseMode(String configValue, boolean rebase,
105 				boolean preserveMerges) {
106 			this.configValue = configValue;
107 			this.rebase = rebase;
108 			this.preserveMerges = preserveMerges;
109 		}
110 
111 		public String toConfigValue() {
112 			return configValue;
113 		}
114 
115 		public boolean matchConfigValue(String in) {
116 			return in.equals(configValue);
117 		}
118 	}
119 
120 	/**
121 	 * @param repo
122 	 */
123 	protected PullCommand(Repository repo) {
124 		super(repo);
125 	}
126 
127 	/**
128 	 * @param monitor
129 	 *            a progress monitor
130 	 * @return this instance
131 	 */
132 	public PullCommand setProgressMonitor(ProgressMonitor monitor) {
133 		this.monitor = monitor;
134 		return this;
135 	}
136 
137 	/**
138 	 * Set if rebase should be used after fetching. If set to true, rebase is
139 	 * used instead of merge. This is equivalent to --rebase on the command
140 	 * line.
141 	 * <p>
142 	 * If set to false, merge is used after fetching, overriding the
143 	 * configuration file. This is equivalent to --no-rebase on the command
144 	 * line.
145 	 * <p>
146 	 * This setting overrides the settings in the configuration file. By
147 	 * default, the setting in the repository configuration file is used.
148 	 * <p>
149 	 * A branch can be configured to use rebase by default. See
150 	 * branch.[name].rebase and branch.autosetuprebase.
151 	 *
152 	 * @param useRebase
153 	 * @return {@code this}
154 	 */
155 	public PullCommand setRebase(boolean useRebase) {
156 		checkCallable();
157 		pullRebaseMode = useRebase ? PullRebaseMode.REBASE : PullRebaseMode.NO_REBASE;
158 		return this;
159 	}
160 
161 	/**
162 	 * Executes the {@code Pull} command with all the options and parameters
163 	 * collected by the setter methods (e.g.
164 	 * {@link #setProgressMonitor(ProgressMonitor)}) of this class. Each
165 	 * instance of this class should only be used for one invocation of the
166 	 * command. Don't call this method twice on an instance.
167 	 *
168 	 * @return the result of the pull
169 	 * @throws WrongRepositoryStateException
170 	 * @throws InvalidConfigurationException
171 	 * @throws DetachedHeadException
172 	 * @throws InvalidRemoteException
173 	 * @throws CanceledException
174 	 * @throws RefNotFoundException
175 	 * @throws RefNotAdvertisedException
176 	 * @throws NoHeadException
177 	 * @throws org.eclipse.jgit.api.errors.TransportException
178 	 * @throws GitAPIException
179 	 */
180 	public PullResult call() throws GitAPIException,
181 			WrongRepositoryStateException, InvalidConfigurationException,
182 			DetachedHeadException, InvalidRemoteException, CanceledException,
183 			RefNotFoundException, RefNotAdvertisedException, NoHeadException,
184 			org.eclipse.jgit.api.errors.TransportException {
185 		checkCallable();
186 
187 		monitor.beginTask(JGitText.get().pullTaskName, 2);
188 
189 		String branchName;
190 		try {
191 			String fullBranch = repo.getFullBranch();
192 			if (fullBranch == null)
193 				throw new NoHeadException(
194 						JGitText.get().pullOnRepoWithoutHEADCurrentlyNotSupported);
195 			if (!fullBranch.startsWith(Constants.R_HEADS)) {
196 				// we can not pull if HEAD is detached and branch is not
197 				// specified explicitly
198 				throw new DetachedHeadException();
199 			}
200 			branchName = fullBranch.substring(Constants.R_HEADS.length());
201 		} catch (IOException e) {
202 			throw new JGitInternalException(
203 					JGitText.get().exceptionCaughtDuringExecutionOfPullCommand,
204 					e);
205 		}
206 
207 		if (!repo.getRepositoryState().equals(RepositoryState.SAFE))
208 			throw new WrongRepositoryStateException(MessageFormat.format(
209 					JGitText.get().cannotPullOnARepoWithState, repo
210 							.getRepositoryState().name()));
211 
212 		Config repoConfig = repo.getConfig();
213 		if (remote == null) {
214 			// get the configured remote for the currently checked out branch
215 			// stored in configuration key branch.<branch name>.remote
216 			remote = repoConfig.getString(
217 					ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
218 					ConfigConstants.CONFIG_KEY_REMOTE);
219 		}
220 		if (remote == null)
221 			// fall back to default remote
222 			remote = Constants.DEFAULT_REMOTE_NAME;
223 
224 		if (remoteBranchName == null)
225 			// get the name of the branch in the remote repository
226 			// stored in configuration key branch.<branch name>.merge
227 			remoteBranchName = repoConfig.getString(
228 					ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
229 					ConfigConstants.CONFIG_KEY_MERGE);
230 
231 		// determines whether rebase should be used after fetching
232 		if (pullRebaseMode == null) {
233 			pullRebaseMode = getRebaseMode(branchName, repoConfig);
234 		}
235 
236 		if (remoteBranchName == null)
237 			remoteBranchName = branchName;
238 
239 		final boolean isRemote = !remote.equals("."); //$NON-NLS-1$
240 		String remoteUri;
241 		FetchResult fetchRes;
242 		if (isRemote) {
243 			remoteUri = repoConfig.getString(
244 					ConfigConstants.CONFIG_REMOTE_SECTION, remote,
245 					ConfigConstants.CONFIG_KEY_URL);
246 			if (remoteUri == null) {
247 				String missingKey = ConfigConstants.CONFIG_REMOTE_SECTION + DOT
248 						+ remote + DOT + ConfigConstants.CONFIG_KEY_URL;
249 				throw new InvalidConfigurationException(MessageFormat.format(
250 						JGitText.get().missingConfigurationForKey, missingKey));
251 			}
252 
253 			if (monitor.isCancelled())
254 				throw new CanceledException(MessageFormat.format(
255 						JGitText.get().operationCanceled,
256 						JGitText.get().pullTaskName));
257 
258 			FetchCommand fetch = new FetchCommand(repo);
259 			fetch.setRemote(remote);
260 			fetch.setProgressMonitor(monitor);
261 			configure(fetch);
262 
263 			fetchRes = fetch.call();
264 		} else {
265 			// we can skip the fetch altogether
266 			remoteUri = JGitText.get().localRepository;
267 			fetchRes = null;
268 		}
269 
270 		monitor.update(1);
271 
272 		if (monitor.isCancelled())
273 			throw new CanceledException(MessageFormat.format(
274 					JGitText.get().operationCanceled,
275 					JGitText.get().pullTaskName));
276 
277 		// we check the updates to see which of the updated branches
278 		// corresponds
279 		// to the remote branch name
280 		AnyObjectId commitToMerge;
281 		if (isRemote) {
282 			Ref r = null;
283 			if (fetchRes != null) {
284 				r = fetchRes.getAdvertisedRef(remoteBranchName);
285 				if (r == null)
286 					r = fetchRes.getAdvertisedRef(Constants.R_HEADS
287 							+ remoteBranchName);
288 			}
289 			if (r == null) {
290 				throw new RefNotAdvertisedException(MessageFormat.format(
291 						JGitText.get().couldNotGetAdvertisedRef, remote,
292 						remoteBranchName));
293 			} else {
294 				commitToMerge = r.getObjectId();
295 			}
296 		} else {
297 			try {
298 				commitToMerge = repo.resolve(remoteBranchName);
299 				if (commitToMerge == null)
300 					throw new RefNotFoundException(MessageFormat.format(
301 							JGitText.get().refNotResolved, remoteBranchName));
302 			} catch (IOException e) {
303 				throw new JGitInternalException(
304 						JGitText.get().exceptionCaughtDuringExecutionOfPullCommand,
305 						e);
306 			}
307 		}
308 
309 		String upstreamName = MessageFormat.format(
310 				JGitText.get().upstreamBranchName,
311 				Repository.shortenRefName(remoteBranchName), remoteUri);
312 
313 		PullResult result;
314 		if (pullRebaseMode.rebase) {
315 			RebaseCommand rebase = new RebaseCommand(repo);
316 			RebaseResult rebaseRes = rebase.setUpstream(commitToMerge)
317 					.setUpstreamName(upstreamName).setProgressMonitor(monitor)
318 					.setOperation(Operation.BEGIN).setStrategy(strategy)
319 					.setPreserveMerges(pullRebaseMode.preserveMerges)
320 					.call();
321 			result = new PullResult(fetchRes, remote, rebaseRes);
322 		} else {
323 			MergeCommand merge = new MergeCommand(repo);
324 			merge.include(upstreamName, commitToMerge);
325 			merge.setStrategy(strategy);
326 			MergeResult mergeRes = merge.call();
327 			monitor.update(1);
328 			result = new PullResult(fetchRes, remote, mergeRes);
329 		}
330 		monitor.endTask();
331 		return result;
332 	}
333 
334 	/**
335 	 * The remote (uri or name) to be used for the pull operation. If no remote
336 	 * is set, the branch's configuration will be used. If the branch
337 	 * configuration is missing the default value of
338 	 * <code>Constants.DEFAULT_REMOTE_NAME</code> will be used.
339 	 *
340 	 * @see Constants#DEFAULT_REMOTE_NAME
341 	 * @param remote
342 	 * @return {@code this}
343 	 * @since 3.3
344 	 */
345 	public PullCommand setRemote(String remote) {
346 		checkCallable();
347 		this.remote = remote;
348 		return this;
349 	}
350 
351 	/**
352 	 * The remote branch name to be used for the pull operation. If no
353 	 * remoteBranchName is set, the branch's configuration will be used. If the
354 	 * branch configuration is missing the remote branch with the same name as
355 	 * the current branch is used.
356 	 *
357 	 * @param remoteBranchName
358 	 * @return {@code this}
359 	 * @since 3.3
360 	 */
361 	public PullCommand setRemoteBranchName(String remoteBranchName) {
362 		checkCallable();
363 		this.remoteBranchName = remoteBranchName;
364 		return this;
365 	}
366 
367 	/**
368 	 * @return the remote used for the pull operation if it was set explicitly
369 	 * @since 3.3
370 	 */
371 	public String getRemote() {
372 		return remote;
373 	}
374 
375 	/**
376 	 * @return the remote branch name used for the pull operation if it was set
377 	 *         explicitly
378 	 * @since 3.3
379 	 */
380 	public String getRemoteBranchName() {
381 		return remoteBranchName;
382 	}
383 
384 	/**
385 	 * @param strategy
386 	 *            The merge strategy to use during this pull operation.
387 	 * @return {@code this}
388 	 * @since 3.4
389 	 */
390 	public PullCommand setStrategy(MergeStrategy strategy) {
391 		this.strategy = strategy;
392 		return this;
393 	}
394 
395 	private static PullRebaseMode getRebaseMode(String branchName, Config config) {
396 		PullRebaseMode mode = config.getEnum(PullRebaseMode.values(),
397 				ConfigConstants.CONFIG_PULL_SECTION, null,
398 				ConfigConstants.CONFIG_KEY_REBASE, PullRebaseMode.NO_REBASE);
399 		mode = config.getEnum(PullRebaseMode.values(),
400 				ConfigConstants.CONFIG_BRANCH_SECTION,
401 				branchName, ConfigConstants.CONFIG_KEY_REBASE, mode);
402 		return mode;
403 	}
404 }