Eclipseplugins
CheckoutOperation.java
1 package com.proalpha.pds.gitutils.common;
2 
3 import java.io.IOException;
4 import java.lang.reflect.InvocationTargetException;
5 import java.net.URISyntaxException;
6 import java.text.MessageFormat;
7 import java.util.Collection;
8 import java.util.List;
9 
10 import org.eclipse.core.runtime.IProgressMonitor;
11 import org.eclipse.egit.core.op.FetchOperation;
12 import org.eclipse.egit.core.securestorage.UserPasswordCredentials;
13 import org.eclipse.egit.ui.internal.SecureStoreUtils;
14 import org.eclipse.egit.ui.internal.credentials.EGitCredentialsProvider;
15 import org.eclipse.jgit.api.CheckoutCommand;
16 import org.eclipse.jgit.api.CheckoutResult;
17 import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
18 import org.eclipse.jgit.api.Git;
19 import org.eclipse.jgit.api.MergeCommand;
20 import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
21 import org.eclipse.jgit.api.MergeResult;
22 import org.eclipse.jgit.api.errors.GitAPIException;
23 import org.eclipse.jgit.errors.InvalidObjectIdException;
24 import org.eclipse.jgit.lib.Constants;
25 import org.eclipse.jgit.lib.Ref;
26 import org.eclipse.jgit.lib.Repository;
27 import org.eclipse.jgit.transport.RefSpec;
28 import org.eclipse.jgit.transport.RemoteConfig;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31 
32 import com.proalpha.git.PaGit;
33 import com.proalpha.git.util.PaBranchName;
34 import com.proalpha.git.util.PaRepository;
35 
36 public class CheckoutOperation {
37 
38  private final Logger logger = LoggerFactory.getLogger(CheckoutOperation.class);
39 
40  protected static final String DEFAULT_SOURCE = "HEAD";
41 
42  protected String targetRef;
43  protected String sourceRef;
44  protected boolean updateTargetRef;
45  protected boolean updateSourceRef;
46 
47  protected String remote;
48 
49  protected Repository repository;
50 
51  protected IProgressMonitor monitor;
52 
53  private CheckoutCommand cmd;
54 
55  protected MergeCommand mergeCmd;
56 
57  protected MergeResult mergeResult;
58 
78  public CheckoutOperation(Repository repository, String remote, String sourceRef, String targetRef,
79  boolean updateSourceRef, boolean updateTargetRef) {
80  this.repository = repository;
81 
82  this.sourceRef = sourceRef;
83  this.targetRef = targetRef;
84 
85  this.updateSourceRef = updateSourceRef;
86  this.updateTargetRef = updateTargetRef;
87 
88  this.remote = remote;
89  PaGit.init(new Git(repository));
90  }
91 
99  public CheckoutOperation(Repository repository, String targetRef) {
100  this(repository, Constants.DEFAULT_REMOTE_NAME, DEFAULT_SOURCE, targetRef, false, false);
101  }
102 
108  public void run() throws InvocationTargetException {
109 
110  if (logger.isInfoEnabled())
111  logger.info(MessageFormat.format("Checkout/creation of branch {0}", this.targetRef));
112 
113  try {
114  if (isLocalBranch(this.targetRef)) {
115  // Checkout existing local branch (with possible fetch to update)
116  checkoutLocalBranch();
117  } else {
118  if (isServerBranch(this.targetRef)) {
119  // Checkout existing server branch
120  checkoutServerBranch();
121  } else {
122  // Create a new branch (with possible fetch of source branch to start
123  // with newest version
124  createNewBranch();
125  }
126  }
127  } finally {
128  PaGit.getInstance().close();
129  }
130 
131  }
132 
144  @SuppressWarnings("restriction")
145  protected boolean tryFetch(String ref) {
146 
147  if (!this.isServerBranch(ref))
148  return false;
149 
150  try {
151  RemoteConfig config = this.getRemoteConfig();
152 
153  if (config == null)
154  return false;
155 
156  RefSpec spec = new RefSpec("+refs/heads/" + ref + ":refs/remotes/" + remote + "/" + ref);
157  config.addFetchRefSpec(spec);
158 
159  FetchOperation op = new FetchOperation(this.repository, config, config.getTimeout(), false /* no dryRun */);
160  op.setCredentialsProvider(getCredentialProvider());
161 
162  op.run(monitor);
163 
164  return true;
165  } catch (URISyntaxException ex) {
166  logger.warn("The remote config seems to be strange", ex);
167  } catch (InvocationTargetException ex) {
168  logger.warn("Error while running the fetch operation ()", ex);
169  }
170 
171  return false;
172  }
173 
178  protected RemoteConfig getRemoteConfig() throws URISyntaxException {
179  List<RemoteConfig> configs = RemoteConfig.getAllRemoteConfigs(repository.getConfig());
180 
181  for (RemoteConfig c : configs) {
182  if (c.getName().equals(this.remote)) {
183  return c;
184  }
185  }
186  return null;
187  }
188 
194  public void setProgressMonitor(IProgressMonitor monitor) {
195  this.monitor = monitor;
196  }
197 
206  public CheckoutResult getResult() {
207 
208  if (cmd != null)
209  if (mergeResult == null /* no merge was performed */ || (mergeResult
210  .getMergeStatus() == MergeResult.MergeStatus.FAST_FORWARD
211  || mergeResult.getMergeStatus() == MergeResult.MergeStatus.MERGED
212  || mergeResult.getMergeStatus() == MergeResult.MergeStatus.ALREADY_UP_TO_DATE))
213  return cmd.getResult();
214  else
215  // Something went wrong during the merge operation: We just indicate
216  // a general error, so the user is informed and must check the
217  // reason by himself
218  return CheckoutResult.ERROR_RESULT;
219 
220  return null;
221  }
222 
230  protected boolean isLocalBranch(String ref) {
231  try {
232  String shortRef = PaBranchName.getShortBranchName(ref);
233  List<Ref> refs = PaGit.getInstance().getGit().branchList().call();
234 
235  for (Ref r : refs) {
236  if (PaBranchName.getShortBranchName(r.getName()).equals(shortRef)) {
237  return true;
238  }
239  }
240 
241  } catch (GitAPIException ex) {
242  logger.error(ex.getMessage(), ex);
243  }
244  return false;
245  }
246 
254  protected boolean isServerBranch(String ref) {
255  try {
256  String shortRef = PaBranchName.getShortBranchName(ref);
257  Collection<Ref> refs = PaGit.getInstance().getGit().lsRemote().setHeads(true).setTags(false)
258  .setCredentialsProvider(getCredentialProvider()).setRemote(this.remote).call();
259 
260  for (Ref r : refs) {
261  if (PaBranchName.getShortBranchName(r.getName()).equals(shortRef)) {
262  return true;
263  }
264  }
265 
266  } catch (GitAPIException ex) {
267  logger.error("Error while retrieving server branches.", ex);
268  }
269  return false;
270  }
271 
277  protected EGitCredentialsProvider getCredentialProvider() {
278 
279  UserPasswordCredentials credentials = null;
280 
281  try {
282  RemoteConfig config = this.getRemoteConfig();
283  try {
284  // NOTE: We assume that exactly one URI is specified in the remote
285  // If this is not the case, we may get the wrong credentials
286  if (config != null)
287  credentials = SecureStoreUtils.getCredentials(config.getURIs().get(0));
288 
289  } catch (IllegalArgumentException ex) {
290  logger.warn("Could not get credentials for the URI of following git repository: {}",
291  (config.getURIs().isEmpty()) ? config.getName() + " - NO URI!" : config.getURIs().get(0));
292  }
293  } catch (URISyntaxException ex) {
294  logger.warn("Could not get credentials for the URI due to URI syntax exception", ex);
295  }
296 
297  if (credentials != null)
298  return new EGitCredentialsProvider(credentials.getUser(), credentials.getPassword());
299  else
300  return new EGitCredentialsProvider();
301 
302  }
303 
312  private void checkoutLocalBranch() throws InvocationTargetException {
313  // Branch is already known in the local repository
314  // --> Checkout and possibly update
315  try {
316  cmd = PaGit.getInstance().getGit().checkout().setName(this.targetRef);
317  cmd.call();
318 
319  if (logger.isInfoEnabled())
320  logger.info(MessageFormat.format("Checkout of existing local branch {0} successful", this.targetRef));
321 
322  if (this.updateTargetRef && tryFetch(this.targetRef)) {
323  // On branches like master, FF merge will result in the same as rebase because there
324  // are no manual commits on client.
325  mergeCmd = PaGit.getInstance().getGit().merge().setFastForward(MergeCommand.FastForwardMode.FF)
326  .include(this.repository.resolve(this.remote + "/" + this.targetRef));
327  mergeResult = mergeCmd.call();
328 
329  if (mergeResult.getMergeStatus().isSuccessful())
330  if (logger.isInfoEnabled())
331  logger.info(MessageFormat.format("Merge of remote branch {0} successful",
332  this.remote + "/" + this.targetRef));
333  else {
334  if (logger.isInfoEnabled())
335  logger.error(MessageFormat.format("Error while updating local ref {0} while merging",
336  this.targetRef));
337  throw new InvocationTargetException(new Throwable("Merge into {0} failed"));
338  }
339  }
340 
341  } catch (GitAPIException | IOException e1) {
342  logger.error(MessageFormat.format("Error while checkout of local branch {0}", this.targetRef), e1);
343  throw new InvocationTargetException(e1.getCause() != null ? e1.getCause() : e1);
344  }
345  }
346 
355  private void checkoutServerBranch() throws InvocationTargetException {
356  try {
357  tryFetch(this.targetRef);
358 
359  cmd = PaGit.getInstance().getGit().checkout().setCreateBranch(true).setName(this.targetRef)
360  .setUpstreamMode(SetupUpstreamMode.TRACK).setStartPoint(this.remote + "/" + this.targetRef);
361  cmd.call();
362 
363  logger.info(MessageFormat.format("Branch {0} created by checkout of remote branch {1}", this.targetRef,
364  this.remote + "/" + this.targetRef));
365  } catch (GitAPIException e1) {
366  logger.error(MessageFormat.format("Error while checkout of server branch {0}", targetRef), e1);
367  throw new InvocationTargetException(e1.getCause() != null ? e1.getCause() : e1);
368  }
369  }
370 
377  private void createNewBranch() throws InvocationTargetException {
378  try {
379  if (this.updateSourceRef && tryFetch(this.sourceRef) && !PaRepository.isRemoteTrackingBranch(this.sourceRef)) {
380  try {
381  PaGit.getInstance().getGit().checkout().setCreateBranch(false).setName(this.sourceRef).call();
382  mergeCmd = PaGit.getInstance().getGit().merge().setFastForward(FastForwardMode.FF_ONLY)
383  .include(this.repository.resolve(this.remote + "/" + this.sourceRef));
384  mergeResult = mergeCmd.call();
385 
386  if (mergeResult.getMergeStatus().isSuccessful()) {
387  if (logger.isInfoEnabled())
388  logger.info(MessageFormat.format("Merge of remote branch {0} successful",
389  this.remote + "/" + this.targetRef));
390  }
391  else {
392  if (logger.isInfoEnabled())
393  logger.error(MessageFormat.format("Error while updating local ref {0} while merging",
394  this.targetRef));
395  throw new InvocationTargetException(new Throwable("Merge into {0} failed"));
396  }
397  } catch (InvalidObjectIdException | IOException ioie) {
398  if (logger.isWarnEnabled())
399  logger.warn(MessageFormat.format("Source ref {0} could not be updated and/or merged into {1}.",
400  this.sourceRef, this.targetRef), ioie);
401  }
402  }
403 
404  cmd = PaGit.getInstance().getGit().checkout().setCreateBranch(true).setName(this.targetRef)
405  .setUpstreamMode(SetupUpstreamMode.NOTRACK).setStartPoint(this.sourceRef);
406  cmd.call();
407 
408  if (logger.isInfoEnabled())
409  logger.info(MessageFormat.format("Creation of branch {0} by branching-off local branch {1}", this.targetRef, this.sourceRef));
410  } catch (GitAPIException e1) {
411  if (logger.isErrorEnabled())
412  logger.error(MessageFormat.format("Error while creating new local branch {0}", targetRef), e1);
413  throw new InvocationTargetException(e1.getCause() != null ? e1.getCause() : e1);
414  }
415  }
416 }
CheckoutOperation(Repository repository, String targetRef)
CheckoutOperation(Repository repository, String remote, String sourceRef, String targetRef, boolean updateSourceRef, boolean updateTargetRef)