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 		if (monitor == null) {
134 			monitor = NullProgressMonitor.INSTANCE;
135 		}
136 		this.monitor = monitor;
137 		return this;
138 	}
139 
140 	/**
141 	 * Set if rebase should be used after fetching. If set to true, rebase is
142 	 * used instead of merge. This is equivalent to --rebase on the command
143 	 * line.
144 	 * <p>
145 	 * If set to false, merge is used after fetching, overriding the
146 	 * configuration file. This is equivalent to --no-rebase on the command
147 	 * line.
148 	 * <p>
149 	 * This setting overrides the settings in the configuration file. By
150 	 * default, the setting in the repository configuration file is used.
151 	 * <p>
152 	 * A branch can be configured to use rebase by default. See
153 	 * branch.[name].rebase and branch.autosetuprebase.
154 	 *
155 	 * @param useRebase
156 	 * @return {@code this}
157 	 */
158 	public PullCommand setRebase(boolean useRebase) {
159 		checkCallable();
160 		pullRebaseMode = useRebase ? PullRebaseMode.REBASE : PullRebaseMode.NO_REBASE;
161 		return this;
162 	}
163 
164 	/**
165 	 * Executes the {@code Pull} command with all the options and parameters
166 	 * collected by the setter methods (e.g.
167 	 * {@link #setProgressMonitor(ProgressMonitor)}) of this class. Each
168 	 * instance of this class should only be used for one invocation of the
169 	 * command. Don't call this method twice on an instance.
170 	 *
171 	 * @return the result of the pull
172 	 * @throws WrongRepositoryStateException
173 	 * @throws InvalidConfigurationException
174 	 * @throws DetachedHeadException
175 	 * @throws InvalidRemoteException
176 	 * @throws CanceledException
177 	 * @throws RefNotFoundException
178 	 * @throws RefNotAdvertisedException
179 	 * @throws NoHeadException
180 	 * @throws org.eclipse.jgit.api.errors.TransportException
181 	 * @throws GitAPIException
182 	 */
183 	public PullResult call() throws GitAPIException,
184 			WrongRepositoryStateException, InvalidConfigurationException,
185 			DetachedHeadException, InvalidRemoteException, CanceledException,
186 			RefNotFoundException, RefNotAdvertisedException, NoHeadException,
187 			org.eclipse.jgit.api.errors.TransportException {
188 		checkCallable();
189 
190 		monitor.beginTask(JGitText.get().pullTaskName, 2);
191 
192 		String branchName;
193 		try {
194 			String fullBranch = repo.getFullBranch();
195 			if (fullBranch == null)
196 				throw new NoHeadException(
197 						JGitText.get().pullOnRepoWithoutHEADCurrentlyNotSupported);
198 			if (!fullBranch.startsWith(Constants.R_HEADS)) {
199 				// we can not pull if HEAD is detached and branch is not
200 				// specified explicitly
201 				throw new DetachedHeadException();
202 			}
203 			branchName = fullBranch.substring(Constants.R_HEADS.length());
204 		} catch (IOException e) {
205 			throw new JGitInternalException(
206 					JGitText.get().exceptionCaughtDuringExecutionOfPullCommand,
207 					e);
208 		}
209 
210 		if (!repo.getRepositoryState().equals(RepositoryState.SAFE))
211 			throw new WrongRepositoryStateException(MessageFormat.format(
212 					JGitText.get().cannotPullOnARepoWithState, repo
213 							.getRepositoryState().name()));
214 
215 		Config repoConfig = repo.getConfig();
216 		if (remote == null) {
217 			// get the configured remote for the currently checked out branch
218 			// stored in configuration key branch.<branch name>.remote
219 			remote = repoConfig.getString(
220 					ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
221 					ConfigConstants.CONFIG_KEY_REMOTE);
222 		}
223 		if (remote == null)
224 			// fall back to default remote
225 			remote = Constants.DEFAULT_REMOTE_NAME;
226 
227 		if (remoteBranchName == null)
228 			// get the name of the branch in the remote repository
229 			// stored in configuration key branch.<branch name>.merge
230 			remoteBranchName = repoConfig.getString(
231 					ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
232 					ConfigConstants.CONFIG_KEY_MERGE);
233 
234 		// determines whether rebase should be used after fetching
235 		if (pullRebaseMode == null) {
236 			pullRebaseMode = getRebaseMode(branchName, repoConfig);
237 		}
238 
239 		if (remoteBranchName == null)
240 			remoteBranchName = branchName;
241 
242 		final boolean isRemote = !remote.equals("."); //$NON-NLS-1$
243 		String remoteUri;
244 		FetchResult fetchRes;
245 		if (isRemote) {
246 			remoteUri = repoConfig.getString(
247 					ConfigConstants.CONFIG_REMOTE_SECTION, remote,
248 					ConfigConstants.CONFIG_KEY_URL);
249 			if (remoteUri == null) {
250 				String missingKey = ConfigConstants.CONFIG_REMOTE_SECTION + DOT
251 						+ remote + DOT + ConfigConstants.CONFIG_KEY_URL;
252 				throw new InvalidConfigurationException(MessageFormat.format(
253 						JGitText.get().missingConfigurationForKey, missingKey));
254 			}
255 
256 			if (monitor.isCancelled())
257 				throw new CanceledException(MessageFormat.format(
258 						JGitText.get().operationCanceled,
259 						JGitText.get().pullTaskName));
260 
261 			FetchCommand fetch = new FetchCommand(repo);
262 			fetch.setRemote(remote);
263 			fetch.setProgressMonitor(monitor);
264 			configure(fetch);
265 
266 			fetchRes = fetch.call();
267 		} else {
268 			// we can skip the fetch altogether
269 			remoteUri = JGitText.get().localRepository;
270 			fetchRes = null;
271 		}
272 
273 		monitor.update(1);
274 
275 		if (monitor.isCancelled())
276 			throw new CanceledException(MessageFormat.format(
277 					JGitText.get().operationCanceled,
278 					JGitText.get().pullTaskName));
279 
280 		// we check the updates to see which of the updated branches
281 		// corresponds
282 		// to the remote branch name
283 		AnyObjectId commitToMerge;
284 		if (isRemote) {
285 			Ref r = null;
286 			if (fetchRes != null) {
287 				r = fetchRes.getAdvertisedRef(remoteBranchName);
288 				if (r == null)
289 					r = fetchRes.getAdvertisedRef(Constants.R_HEADS
290 							+ remoteBranchName);
291 			}
292 			if (r == null) {
293 				throw new RefNotAdvertisedException(MessageFormat.format(
294 						JGitText.get().couldNotGetAdvertisedRef, remote,
295 						remoteBranchName));
296 			} else {
297 				commitToMerge = r.getObjectId();
298 			}
299 		} else {
300 			try {
301 				commitToMerge = repo.resolve(remoteBranchName);
302 				if (commitToMerge == null)
303 					throw new RefNotFoundException(MessageFormat.format(
304 							JGitText.get().refNotResolved, remoteBranchName));
305 			} catch (IOException e) {
306 				throw new JGitInternalException(
307 						JGitText.get().exceptionCaughtDuringExecutionOfPullCommand,
308 						e);
309 			}
310 		}
311 
312 		String upstreamName = MessageFormat.format(
313 				JGitText.get().upstreamBranchName,
314 				Repository.shortenRefName(remoteBranchName), remoteUri);
315 
316 		PullResult result;
317 		if (pullRebaseMode.rebase) {
318 			RebaseCommand rebase = new RebaseCommand(repo);
319 			RebaseResult rebaseRes = rebase.setUpstream(commitToMerge)
320 					.setUpstreamName(upstreamName).setProgressMonitor(monitor)
321 					.setOperation(Operation.BEGIN).setStrategy(strategy)
322 					.setPreserveMerges(pullRebaseMode.preserveMerges)
323 					.call();
324 			result = new PullResult(fetchRes, remote, rebaseRes);
325 		} else {
326 			MergeCommand merge = new MergeCommand(repo);
327 			merge.include(upstreamName, commitToMerge);
328 			merge.setStrategy(strategy);
329 			MergeResult mergeRes = merge.call();
330 			monitor.update(1);
331 			result = new PullResult(fetchRes, remote, mergeRes);
332 		}
333 		monitor.endTask();
334 		return result;
335 	}
336 
337 	/**
338 	 * The remote (uri or name) to be used for the pull operation. If no remote
339 	 * is set, the branch's configuration will be used. If the branch
340 	 * configuration is missing the default value of
341 	 * <code>Constants.DEFAULT_REMOTE_NAME</code> will be used.
342 	 *
343 	 * @see Constants#DEFAULT_REMOTE_NAME
344 	 * @param remote
345 	 * @return {@code this}
346 	 * @since 3.3
347 	 */
348 	public PullCommand setRemote(String remote) {
349 		checkCallable();
350 		this.remote = remote;
351 		return this;
352 	}
353 
354 	/**
355 	 * The remote branch name to be used for the pull operation. If no
356 	 * remoteBranchName is set, the branch's configuration will be used. If the
357 	 * branch configuration is missing the remote branch with the same name as
358 	 * the current branch is used.
359 	 *
360 	 * @param remoteBranchName
361 	 * @return {@code this}
362 	 * @since 3.3
363 	 */
364 	public PullCommand setRemoteBranchName(String remoteBranchName) {
365 		checkCallable();
366 		this.remoteBranchName = remoteBranchName;
367 		return this;
368 	}
369 
370 	/**
371 	 * @return the remote used for the pull operation if it was set explicitly
372 	 * @since 3.3
373 	 */
374 	public String getRemote() {
375 		return remote;
376 	}
377 
378 	/**
379 	 * @return the remote branch name used for the pull operation if it was set
380 	 *         explicitly
381 	 * @since 3.3
382 	 */
383 	public String getRemoteBranchName() {
384 		return remoteBranchName;
385 	}
386 
387 	/**
388 	 * @param strategy
389 	 *            The merge strategy to use during this pull operation.
390 	 * @return {@code this}
391 	 * @since 3.4
392 	 */
393 	public PullCommand setStrategy(MergeStrategy strategy) {
394 		this.strategy = strategy;
395 		return this;
396 	}
397 
398 	private static PullRebaseMode getRebaseMode(String branchName, Config config) {
399 		PullRebaseMode mode = config.getEnum(PullRebaseMode.values(),
400 				ConfigConstants.CONFIG_PULL_SECTION, null,
401 				ConfigConstants.CONFIG_KEY_REBASE, PullRebaseMode.NO_REBASE);
402 		mode = config.getEnum(PullRebaseMode.values(),
403 				ConfigConstants.CONFIG_BRANCH_SECTION,
404 				branchName, ConfigConstants.CONFIG_KEY_REBASE, mode);
405 		return mode;
406 	}
407 }