PageRenderTime 127ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java

https://gitlab.com/vectorci/git-client-plugin
Java | 1359 lines | 1177 code | 81 blank | 101 comment | 113 complexity | 9d95da254f073b26762a0244162d7369 MD5 | raw file
  1. package org.jenkinsci.plugins.gitclient;
  2. import static org.apache.commons.lang.StringUtils.isBlank;
  3. import static org.apache.commons.lang.StringUtils.removeStart;
  4. import static org.eclipse.jgit.api.ResetCommand.ResetType.HARD;
  5. import static org.eclipse.jgit.api.ResetCommand.ResetType.MIXED;
  6. import static org.eclipse.jgit.lib.Constants.CHARSET;
  7. import static org.eclipse.jgit.lib.Constants.HEAD;
  8. import static org.eclipse.jgit.lib.Constants.R_HEADS;
  9. import static org.eclipse.jgit.lib.Constants.R_REMOTES;
  10. import static org.eclipse.jgit.lib.Constants.R_TAGS;
  11. import static org.eclipse.jgit.lib.Constants.typeString;
  12. import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.OK;
  13. import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.UP_TO_DATE;
  14. import hudson.FilePath;
  15. import hudson.Util;
  16. import hudson.model.TaskListener;
  17. import hudson.plugins.git.Branch;
  18. import hudson.plugins.git.GitException;
  19. import hudson.plugins.git.GitLockFailedException;
  20. import hudson.plugins.git.IndexEntry;
  21. import hudson.plugins.git.Revision;
  22. import hudson.util.IOUtils;
  23. import java.io.File;
  24. import java.io.FileNotFoundException;
  25. import java.io.IOException;
  26. import java.io.InputStreamReader;
  27. import java.io.PrintWriter;
  28. import java.io.StringWriter;
  29. import java.io.Writer;
  30. import java.net.URISyntaxException;
  31. import java.util.ArrayList;
  32. import java.util.Arrays;
  33. import java.util.Collection;
  34. import java.util.Collections;
  35. import java.util.Comparator;
  36. import java.util.Date;
  37. import java.util.HashMap;
  38. import java.util.HashSet;
  39. import java.util.List;
  40. import java.util.ListIterator;
  41. import java.util.Map;
  42. import java.util.Set;
  43. import java.util.regex.Pattern;
  44. import javax.annotation.Nullable;
  45. import org.apache.commons.lang.time.FastDateFormat;
  46. import org.eclipse.jgit.api.AddNoteCommand;
  47. import org.eclipse.jgit.api.CommitCommand;
  48. import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
  49. import org.eclipse.jgit.api.FetchCommand;
  50. import org.eclipse.jgit.api.Git;
  51. import org.eclipse.jgit.api.ListBranchCommand;
  52. import org.eclipse.jgit.api.LsRemoteCommand;
  53. import org.eclipse.jgit.api.MergeResult;
  54. import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
  55. import org.eclipse.jgit.api.ResetCommand;
  56. import org.eclipse.jgit.api.ShowNoteCommand;
  57. import org.eclipse.jgit.api.errors.CheckoutConflictException;
  58. import org.eclipse.jgit.api.errors.GitAPIException;
  59. import org.eclipse.jgit.api.errors.JGitInternalException;
  60. import org.eclipse.jgit.diff.DiffEntry;
  61. import org.eclipse.jgit.diff.DiffEntry.ChangeType;
  62. import org.eclipse.jgit.diff.RenameDetector;
  63. import org.eclipse.jgit.errors.InvalidPatternException;
  64. import org.eclipse.jgit.errors.LockFailedException;
  65. import org.eclipse.jgit.errors.NotSupportedException;
  66. import org.eclipse.jgit.errors.TransportException;
  67. import org.eclipse.jgit.fnmatch.FileNameMatcher;
  68. import org.eclipse.jgit.internal.storage.file.FileRepository;
  69. import org.eclipse.jgit.lib.Config;
  70. import org.eclipse.jgit.lib.Constants;
  71. import org.eclipse.jgit.lib.ObjectId;
  72. import org.eclipse.jgit.lib.ObjectLoader;
  73. import org.eclipse.jgit.lib.ObjectReader;
  74. import org.eclipse.jgit.lib.PersonIdent;
  75. import org.eclipse.jgit.lib.Ref;
  76. import org.eclipse.jgit.lib.RefDatabase;
  77. import org.eclipse.jgit.lib.RefUpdate;
  78. import org.eclipse.jgit.lib.RefUpdate.Result;
  79. import org.eclipse.jgit.lib.Repository;
  80. import org.eclipse.jgit.lib.RepositoryBuilder;
  81. import org.eclipse.jgit.lib.StoredConfig;
  82. import org.eclipse.jgit.merge.MergeStrategy;
  83. import org.eclipse.jgit.notes.Note;
  84. import org.eclipse.jgit.revwalk.RevCommit;
  85. import org.eclipse.jgit.revwalk.RevFlag;
  86. import org.eclipse.jgit.revwalk.RevFlagSet;
  87. import org.eclipse.jgit.revwalk.RevObject;
  88. import org.eclipse.jgit.revwalk.RevSort;
  89. import org.eclipse.jgit.revwalk.RevTree;
  90. import org.eclipse.jgit.revwalk.RevWalk;
  91. import org.eclipse.jgit.revwalk.filter.MaxCountRevFilter;
  92. import org.eclipse.jgit.revwalk.filter.RevFilter;
  93. import org.eclipse.jgit.submodule.SubmoduleWalk;
  94. import org.eclipse.jgit.transport.CredentialsProvider;
  95. import org.eclipse.jgit.transport.FetchConnection;
  96. import org.eclipse.jgit.transport.RefSpec;
  97. import org.eclipse.jgit.transport.RemoteConfig;
  98. import org.eclipse.jgit.transport.SshSessionFactory;
  99. import org.eclipse.jgit.transport.TagOpt;
  100. import org.eclipse.jgit.transport.Transport;
  101. import org.eclipse.jgit.transport.URIish;
  102. import org.eclipse.jgit.transport.PushResult;
  103. import org.eclipse.jgit.transport.RemoteRefUpdate;
  104. import org.eclipse.jgit.treewalk.TreeWalk;
  105. import org.eclipse.jgit.treewalk.filter.TreeFilter;
  106. import org.jenkinsci.plugins.gitclient.trilead.SmartCredentialsProvider;
  107. import org.jenkinsci.plugins.gitclient.trilead.TrileadSessionFactory;
  108. import com.cloudbees.plugins.credentials.common.StandardCredentials;
  109. import com.google.common.base.Functions;
  110. import com.google.common.base.Predicate;
  111. import com.google.common.base.Predicates;
  112. import com.google.common.collect.Iterables;
  113. import com.google.common.collect.Lists;
  114. import edu.umd.cs.findbugs.annotations.NonNull;
  115. import org.eclipse.jgit.api.RebaseCommand.Operation;
  116. import org.eclipse.jgit.api.RebaseResult;
  117. /**
  118. * GitClient pure Java implementation using JGit.
  119. * Goal is to eventually get a full java implementation for GitClient
  120. * <b>
  121. * For internal use only, don't use directly. See {@link org.jenkinsci.plugins.gitclient.Git}
  122. * </b>
  123. *
  124. * @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
  125. * @author Kohsuke Kawaguchi
  126. */
  127. public class JGitAPIImpl extends LegacyCompatibleGitAPIImpl {
  128. private static final long serialVersionUID = 1L;
  129. private final TaskListener listener;
  130. private PersonIdent author, committer;
  131. private transient CredentialsProvider provider;
  132. JGitAPIImpl(File workspace, TaskListener listener) {
  133. /* If workspace is null, then default to current directory to match
  134. * CliGitAPIImpl behavior */
  135. super(workspace == null ? new File(".") : workspace);
  136. this.listener = listener;
  137. // to avoid rogue plugins from clobbering what we use, always
  138. // make a point of overwriting it with ours.
  139. SshSessionFactory.setInstance(new TrileadSessionFactory());
  140. }
  141. /**
  142. * clearCredentials.
  143. */
  144. public void clearCredentials() {
  145. asSmartCredentialsProvider().clearCredentials();
  146. }
  147. /** {@inheritDoc} */
  148. public void addCredentials(String url, StandardCredentials credentials) {
  149. asSmartCredentialsProvider().addCredentials(url, credentials);
  150. }
  151. /** {@inheritDoc} */
  152. public void addDefaultCredentials(StandardCredentials credentials) {
  153. asSmartCredentialsProvider().addDefaultCredentials(credentials);
  154. }
  155. private synchronized SmartCredentialsProvider asSmartCredentialsProvider() {
  156. if (!(provider instanceof SmartCredentialsProvider)) {
  157. provider = new SmartCredentialsProvider(listener);
  158. }
  159. return ((SmartCredentialsProvider) provider);
  160. }
  161. /**
  162. * setCredentialsProvider.
  163. *
  164. * @param prov a {@link org.eclipse.jgit.transport.CredentialsProvider} object.
  165. */
  166. public synchronized void setCredentialsProvider(CredentialsProvider prov) {
  167. this.provider = prov;
  168. }
  169. private synchronized CredentialsProvider getProvider() {
  170. return provider;
  171. }
  172. /** {@inheritDoc} */
  173. public GitClient subGit(String subdir) {
  174. return new JGitAPIImpl(new File(workspace, subdir), listener);
  175. }
  176. /** {@inheritDoc} */
  177. public void setAuthor(String name, String email) throws GitException {
  178. author = new PersonIdent(name,email);
  179. }
  180. /** {@inheritDoc} */
  181. public void setCommitter(String name, String email) throws GitException {
  182. committer = new PersonIdent(name,email);
  183. }
  184. /**
  185. * init.
  186. *
  187. * @throws hudson.plugins.git.GitException if underlying git operation fails.
  188. * @throws java.lang.InterruptedException if interrupted.
  189. */
  190. public void init() throws GitException, InterruptedException {
  191. init_().workspace(workspace.getAbsolutePath()).execute();
  192. }
  193. private void doInit(String workspace, boolean bare) throws GitException {
  194. try {
  195. Git.init().setBare(bare).setDirectory(new File(workspace)).call();
  196. } catch (GitAPIException e) {
  197. throw new GitException(e);
  198. }
  199. }
  200. /**
  201. * checkout.
  202. *
  203. * @return a {@link org.jenkinsci.plugins.gitclient.CheckoutCommand} object.
  204. */
  205. public CheckoutCommand checkout() {
  206. return new CheckoutCommand() {
  207. public String ref;
  208. public String branch;
  209. public boolean deleteBranch;
  210. public List<String> sparseCheckoutPaths = Collections.emptyList();
  211. public CheckoutCommand ref(String ref) {
  212. this.ref = ref;
  213. return this;
  214. }
  215. public CheckoutCommand branch(String branch) {
  216. this.branch = branch;
  217. return this;
  218. }
  219. public CheckoutCommand deleteBranchIfExist(boolean deleteBranch) {
  220. this.deleteBranch = deleteBranch;
  221. return this;
  222. }
  223. public CheckoutCommand sparseCheckoutPaths(List<String> sparseCheckoutPaths) {
  224. this.sparseCheckoutPaths = sparseCheckoutPaths == null ? Collections.<String>emptyList() : sparseCheckoutPaths;
  225. return this;
  226. }
  227. public CheckoutCommand timeout(Integer timeout) {
  228. // noop in jgit
  229. return this;
  230. }
  231. public void execute() throws GitException, InterruptedException {
  232. if(! sparseCheckoutPaths.isEmpty()) {
  233. listener.getLogger().println("[ERROR] JGit doesn't support sparse checkout.");
  234. throw new UnsupportedOperationException("not implemented yet");
  235. }
  236. if (branch == null)
  237. doCheckout(ref);
  238. else if (deleteBranch)
  239. doCheckoutCleanBranch(branch, ref);
  240. else
  241. doCheckout(ref, branch);
  242. }
  243. };
  244. }
  245. private void doCheckout(String ref) throws GitException {
  246. boolean retried = false;
  247. Repository repo = null;
  248. while (true) {
  249. try {
  250. repo = getRepository();
  251. try {
  252. // force in Jgit is "-B" in Git CLI, meaning no forced switch,
  253. // but forces recreation of the branch.
  254. // we need to take back all open changes to get the equivalent
  255. // of git checkout -f
  256. git(repo).reset().setMode(HARD).call();
  257. } catch (GitAPIException e) {
  258. throw new GitException("Could not reset the workspace before checkout of " + ref, e);
  259. } catch (JGitInternalException e) {
  260. if (e.getCause() instanceof LockFailedException){
  261. throw new GitLockFailedException("Could not lock repository. Please try again", e);
  262. } else {
  263. throw e;
  264. }
  265. }
  266. if (repo.resolve(ref) != null) {
  267. // ref is either an existing reference or a shortcut to a tag or branch (without refs/heads/)
  268. git(repo).checkout().setName(ref).setForce(true).call();
  269. return;
  270. }
  271. List<String> remoteTrackingBranches = new ArrayList<String>();
  272. for (String remote : repo.getRemoteNames()) {
  273. // look for exactly ONE remote tracking branch
  274. String matchingRemoteBranch = Constants.R_REMOTES + remote + "/" + ref;
  275. if (repo.getRef(matchingRemoteBranch) != null) {
  276. remoteTrackingBranches.add(matchingRemoteBranch);
  277. }
  278. }
  279. if (remoteTrackingBranches.isEmpty()) {
  280. throw new GitException("No matching revision for " + ref + " found.");
  281. }
  282. if (remoteTrackingBranches.size() > 1) {
  283. throw new GitException("Found more than one matching remote tracking branches for " + ref + " : " + remoteTrackingBranches);
  284. }
  285. String matchingRemoteBranch = remoteTrackingBranches.get(0);
  286. listener.getLogger().format("[WARNING] Automatically creating a local branch '%s' tracking remote branch '%s'", ref, removeStart(matchingRemoteBranch, Constants.R_REMOTES));
  287. git(repo).checkout()
  288. .setCreateBranch(true)
  289. .setName(ref)
  290. .setUpstreamMode(SetupUpstreamMode.SET_UPSTREAM)
  291. .setStartPoint(matchingRemoteBranch).call();
  292. return;
  293. } catch (CheckoutConflictException e) {
  294. if (repo != null) {
  295. repo.close(); /* Close and null for immediate reuse */
  296. repo = null;
  297. }
  298. // "git checkout -f" seems to overwrite local untracked files but git CheckoutCommand doesn't.
  299. // see the test case GitAPITestCase.test_localCheckoutConflict. so in this case we manually
  300. // clean up the conflicts and try it again
  301. if (retried)
  302. throw new GitException("Could not checkout " + ref, e);
  303. retried = true;
  304. repo = getRepository(); /* Reusing repo declared and assigned earlier */
  305. for (String path : e.getConflictingPaths()) {
  306. File conflict = new File(repo.getWorkTree(), path);
  307. if (!conflict.delete() && conflict.exists()) {
  308. listener.getLogger().println("[WARNING] conflicting path " + conflict + " not deleted");
  309. }
  310. }
  311. } catch (IOException e) {
  312. throw new GitException("Could not checkout " + ref, e);
  313. } catch (GitAPIException e) {
  314. throw new GitException("Could not checkout " + ref, e);
  315. } catch (JGitInternalException e) {
  316. if (Pattern.matches("Cannot lock.+", e.getMessage())){
  317. throw new GitLockFailedException("Could not lock repository. Please try again", e);
  318. } else {
  319. throw e;
  320. }
  321. } finally {
  322. if (repo != null) repo.close();
  323. }
  324. }
  325. }
  326. private void doCheckout(String ref, String branch) throws GitException {
  327. Repository repo = null;
  328. try {
  329. repo = getRepository();
  330. if (ref == null) ref = repo.resolve(HEAD).name();
  331. git(repo).checkout().setName(branch).setCreateBranch(true).setForce(true).setStartPoint(ref).call();
  332. } catch (IOException e) {
  333. throw new GitException("Could not checkout " + branch + " with start point " + ref, e);
  334. } catch (GitAPIException e) {
  335. throw new GitException("Could not checkout " + branch + " with start point " + ref, e);
  336. } finally {
  337. if (repo != null) repo.close();
  338. }
  339. }
  340. private void doCheckoutCleanBranch(String branch, String ref) throws GitException {
  341. Repository repo = null;
  342. try {
  343. repo = getRepository();
  344. RefUpdate refUpdate = repo.updateRef(R_HEADS + branch);
  345. refUpdate.setNewObjectId(repo.resolve(ref));
  346. switch (refUpdate.forceUpdate()) {
  347. case NOT_ATTEMPTED:
  348. case LOCK_FAILURE:
  349. case REJECTED:
  350. case REJECTED_CURRENT_BRANCH:
  351. case IO_FAILURE:
  352. case RENAMED:
  353. throw new GitException("Could not update " + branch + " to " + ref);
  354. }
  355. doCheckout(branch);
  356. } catch (IOException e) {
  357. throw new GitException("Could not checkout " + branch + " with start point " + ref, e);
  358. } finally {
  359. if (repo != null) repo.close();
  360. }
  361. }
  362. /** {@inheritDoc} */
  363. public void add(String filePattern) throws GitException {
  364. Repository repo = null;
  365. try {
  366. repo = getRepository();
  367. git(repo).add().addFilepattern(filePattern).call();
  368. } catch (GitAPIException e) {
  369. throw new GitException(e);
  370. } finally {
  371. if (repo != null) repo.close();
  372. }
  373. }
  374. private Git git(Repository repo) {
  375. return Git.wrap(repo);
  376. }
  377. /** {@inheritDoc} */
  378. public void commit(String message) throws GitException {
  379. Repository repo = null;
  380. try {
  381. repo = getRepository();
  382. CommitCommand cmd = git(repo).commit().setMessage(message);
  383. if (author!=null)
  384. cmd.setAuthor(author);
  385. if (committer!=null)
  386. cmd.setCommitter(new PersonIdent(committer,new Date()));
  387. cmd.call();
  388. } catch (GitAPIException e) {
  389. throw new GitException(e);
  390. } finally {
  391. if (repo != null) repo.close();
  392. }
  393. }
  394. /** {@inheritDoc} */
  395. public void branch(String name) throws GitException {
  396. Repository repo = null;
  397. try {
  398. repo = getRepository();
  399. git(repo).branchCreate().setName(name).call();
  400. } catch (GitAPIException e) {
  401. throw new GitException(e);
  402. } finally {
  403. if (repo != null) repo.close();
  404. }
  405. }
  406. /** {@inheritDoc} */
  407. public void deleteBranch(String name) throws GitException {
  408. Repository repo = null;
  409. try {
  410. repo = getRepository();
  411. git(repo).branchDelete().setForce(true).setBranchNames(name).call();
  412. } catch (GitAPIException e) {
  413. throw new GitException(e);
  414. } finally {
  415. if (repo != null) repo.close();
  416. }
  417. }
  418. /**
  419. * getBranches.
  420. *
  421. * @return a {@link java.util.Set} object.
  422. * @throws hudson.plugins.git.GitException if underlying git operation fails.
  423. */
  424. public Set<Branch> getBranches() throws GitException {
  425. Repository repo = null;
  426. try {
  427. repo = getRepository();
  428. List<Ref> refs = git(repo).branchList().setListMode(ListBranchCommand.ListMode.ALL).call();
  429. Set<Branch> branches = new HashSet<Branch>(refs.size());
  430. for (Ref ref : refs) {
  431. branches.add(new Branch(ref));
  432. }
  433. return branches;
  434. } catch (GitAPIException e) {
  435. throw new GitException(e);
  436. } finally {
  437. if (repo != null) repo.close();
  438. }
  439. }
  440. /**
  441. * getRemoteBranches.
  442. *
  443. * @return a {@link java.util.Set} object.
  444. * @throws hudson.plugins.git.GitException if underlying git operation fails.
  445. */
  446. public Set<Branch> getRemoteBranches() throws GitException {
  447. Repository repo = null;
  448. try {
  449. repo = getRepository();
  450. List<Ref> refs = git(repo).branchList().setListMode(ListBranchCommand.ListMode.REMOTE).call();
  451. Set<Branch> branches = new HashSet<Branch>(refs.size());
  452. for (Ref ref : refs) {
  453. branches.add(new Branch(ref));
  454. }
  455. return branches;
  456. } catch (GitAPIException e) {
  457. throw new GitException(e);
  458. } finally {
  459. if (repo != null) repo.close();
  460. }
  461. }
  462. /** {@inheritDoc} */
  463. public void tag(String name, String message) throws GitException {
  464. Repository repo = null;
  465. try {
  466. repo = getRepository();
  467. git(repo).tag().setName(name).setMessage(message).setForceUpdate(true).call();
  468. } catch (GitAPIException e) {
  469. throw new GitException(e);
  470. } finally {
  471. if (repo != null) repo.close();
  472. }
  473. }
  474. /** {@inheritDoc} */
  475. public boolean tagExists(String tagName) throws GitException {
  476. Repository repo = null;
  477. try {
  478. repo = getRepository();
  479. Ref tag = repo.getRefDatabase().getRef(R_TAGS + tagName);
  480. return tag != null;
  481. } catch (IOException e) {
  482. throw new GitException(e);
  483. } finally {
  484. if (repo != null) repo.close();
  485. }
  486. }
  487. /**
  488. * fetch_.
  489. *
  490. * @return a {@link org.jenkinsci.plugins.gitclient.FetchCommand} object.
  491. */
  492. public org.jenkinsci.plugins.gitclient.FetchCommand fetch_() {
  493. return new org.jenkinsci.plugins.gitclient.FetchCommand() {
  494. public URIish url;
  495. public List<RefSpec> refspecs;
  496. // JGit 3.3.0 thru 3.6.0 prune more branches than expected
  497. // Refer to GitAPITestCase.test_fetch_with_prune()
  498. private boolean shouldPrune = false;
  499. public boolean tags = true;
  500. public org.jenkinsci.plugins.gitclient.FetchCommand from(URIish remote, List<RefSpec> refspecs) {
  501. this.url = remote;
  502. this.refspecs = refspecs;
  503. return this;
  504. }
  505. public org.jenkinsci.plugins.gitclient.FetchCommand prune() {
  506. //throw new UnsupportedOperationException("JGit don't (yet) support pruning during fetch");
  507. shouldPrune = true;
  508. return this;
  509. }
  510. public org.jenkinsci.plugins.gitclient.FetchCommand shallow(boolean shallow) {
  511. if (shallow) {
  512. listener.getLogger().println("[WARNING] JGit doesn't support shallow clone. This flag is ignored");
  513. }
  514. return this;
  515. }
  516. public org.jenkinsci.plugins.gitclient.FetchCommand timeout(Integer timeout) {
  517. // noop in jgit
  518. return this;
  519. }
  520. public org.jenkinsci.plugins.gitclient.FetchCommand tags(boolean tags) {
  521. this.tags = tags;
  522. return this;
  523. }
  524. public org.jenkinsci.plugins.gitclient.FetchCommand depth(Integer depth) {
  525. listener.getLogger().println("[WARNING] JGit doesn't support shallow clone and therefore depth is meaningless. This flag is ignored");
  526. return this;
  527. }
  528. public void execute() throws GitException, InterruptedException {
  529. Repository repo = null;
  530. FetchCommand fetch = null;
  531. try {
  532. repo = getRepository();
  533. Git git = git(repo);
  534. List<RefSpec> refSpecs = new ArrayList<RefSpec>();
  535. if (tags) {
  536. // see http://stackoverflow.com/questions/14876321/jgit-fetch-dont-update-tag
  537. refSpecs.add(new RefSpec("+refs/tags/*:refs/tags/*"));
  538. }
  539. if (refspecs != null)
  540. for (RefSpec rs: refspecs)
  541. if (rs != null)
  542. refSpecs.add(rs);
  543. if (shouldPrune) {
  544. // since prune is broken in JGit, we go the trivial way:
  545. // delete all refs matching the right side of the refspecs
  546. // then fetch and let the git recreate them.
  547. List<Ref> refs = git.branchList().setListMode(ListBranchCommand.ListMode.REMOTE).call();
  548. List<String> toDelete = new ArrayList<String>(refs.size());
  549. for (ListIterator<Ref> it = refs.listIterator(); it.hasNext(); ) {
  550. Ref branchRef = it.next();
  551. for (RefSpec rs : refSpecs) {
  552. if (rs.matchDestination(branchRef)) {
  553. toDelete.add(branchRef.getName());
  554. break;
  555. }
  556. }
  557. }
  558. if (!toDelete.isEmpty()) {
  559. // we need force = true because usually not all remote branches will be merged into the current branch.
  560. git.branchDelete().setForce(true).setBranchNames(toDelete.toArray(new String[toDelete.size()])).call();
  561. }
  562. }
  563. fetch = git.fetch();
  564. fetch.setTagOpt(tags ? TagOpt.FETCH_TAGS : TagOpt.NO_TAGS);
  565. fetch.setRemote(url.toString());
  566. fetch.setCredentialsProvider(getProvider());
  567. fetch.setRefSpecs(refSpecs);
  568. // fetch.setRemoveDeletedRefs(shouldPrune);
  569. fetch.call();
  570. } catch (GitAPIException e) {
  571. throw new GitException(e);
  572. } finally {
  573. if (fetch != null && fetch.getRepository() != null) fetch.getRepository().close();
  574. if (repo != null) repo.close();
  575. }
  576. }
  577. };
  578. }
  579. /**
  580. * {@inheritDoc}
  581. *
  582. * @param url a {@link org.eclipse.jgit.transport.URIish} object.
  583. * @param refspecs a {@link java.util.List} object.
  584. * @throws hudson.plugins.git.GitException if any.
  585. * @throws java.lang.InterruptedException if any.
  586. */
  587. public void fetch(URIish url, List<RefSpec> refspecs) throws GitException, InterruptedException {
  588. fetch_().from(url, refspecs).execute();
  589. }
  590. /** {@inheritDoc} */
  591. public void fetch(String remoteName, RefSpec... refspec) throws GitException {
  592. Repository repo = null;
  593. try {
  594. repo = getRepository();
  595. FetchCommand fetch = git(repo).fetch().setTagOpt(TagOpt.FETCH_TAGS);
  596. if (remoteName != null) fetch.setRemote(remoteName);
  597. fetch.setCredentialsProvider(getProvider());
  598. // see http://stackoverflow.com/questions/14876321/jgit-fetch-dont-update-tag
  599. List<RefSpec> refSpecs = new ArrayList<RefSpec>();
  600. refSpecs.add(new RefSpec("+refs/tags/*:refs/tags/*"));
  601. if (refspec != null && refspec.length > 0)
  602. for (RefSpec rs: refspec)
  603. if (rs != null)
  604. refSpecs.add(rs);
  605. fetch.setRefSpecs(refSpecs);
  606. fetch.call();
  607. } catch (GitAPIException e) {
  608. throw new GitException(e);
  609. } finally {
  610. if (repo != null) repo.close();
  611. }
  612. }
  613. /** {@inheritDoc} */
  614. public void fetch(String remoteName, RefSpec refspec) throws GitException {
  615. fetch(remoteName, new RefSpec[] {refspec});
  616. }
  617. /** {@inheritDoc} */
  618. public void ref(String refName) throws GitException, InterruptedException {
  619. refName = refName.replace(' ', '_');
  620. Repository repo = null;
  621. try {
  622. repo = getRepository();
  623. RefUpdate refUpdate = repo.updateRef(refName);
  624. refUpdate.setNewObjectId(repo.getRef(Constants.HEAD).getObjectId());
  625. switch (refUpdate.forceUpdate()) {
  626. case NOT_ATTEMPTED:
  627. case LOCK_FAILURE:
  628. case REJECTED:
  629. case REJECTED_CURRENT_BRANCH:
  630. case IO_FAILURE:
  631. case RENAMED:
  632. throw new GitException("Could not update " + refName + " to HEAD");
  633. }
  634. } catch (IOException e) {
  635. throw new GitException("Could not update " + refName + " to HEAD", e);
  636. } finally {
  637. if (repo != null) repo.close();
  638. }
  639. }
  640. /** {@inheritDoc} */
  641. public boolean refExists(String refName) throws GitException, InterruptedException {
  642. refName = refName.replace(' ', '_');
  643. Repository repo = null;
  644. try {
  645. repo = getRepository();
  646. Ref ref = repo.getRefDatabase().getRef(refName);
  647. return ref != null;
  648. } catch (IOException e) {
  649. throw new GitException("Error checking ref " + refName, e);
  650. } finally {
  651. if (repo != null) repo.close();
  652. }
  653. }
  654. /** {@inheritDoc} */
  655. public void deleteRef(String refName) throws GitException, InterruptedException {
  656. refName = refName.replace(' ', '_');
  657. Repository repo = null;
  658. try {
  659. repo = getRepository();
  660. RefUpdate refUpdate = repo.updateRef(refName);
  661. // Required, even though this is a forced delete.
  662. refUpdate.setNewObjectId(repo.getRef(Constants.HEAD).getObjectId());
  663. refUpdate.setForceUpdate(true);
  664. switch (refUpdate.delete()) {
  665. case NOT_ATTEMPTED:
  666. case LOCK_FAILURE:
  667. case REJECTED:
  668. case REJECTED_CURRENT_BRANCH:
  669. case IO_FAILURE:
  670. case RENAMED:
  671. throw new GitException("Could not delete " + refName);
  672. }
  673. } catch (IOException e) {
  674. throw new GitException("Could not delete " + refName, e);
  675. } finally {
  676. if (repo != null) repo.close();
  677. }
  678. }
  679. /** {@inheritDoc} */
  680. public Set<String> getRefNames(String refPrefix) throws GitException, InterruptedException {
  681. if (refPrefix.isEmpty()) {
  682. refPrefix = RefDatabase.ALL;
  683. } else {
  684. refPrefix = refPrefix.replace(' ', '_');
  685. }
  686. Repository repo = null;
  687. try {
  688. repo = getRepository();
  689. Map<String, Ref> refList = repo.getRefDatabase().getRefs(refPrefix);
  690. // The key set for refList will have refPrefix removed, so to recover it we just grab the full name.
  691. Set<String> refs = new HashSet<String>(refList.size());
  692. for (Ref ref : refList.values()) {
  693. refs.add(ref.getName());
  694. }
  695. return refs;
  696. } catch (IOException e) {
  697. throw new GitException("Error retrieving refs with prefix " + refPrefix, e);
  698. } finally {
  699. if (repo != null) repo.close();
  700. }
  701. }
  702. /** {@inheritDoc} */
  703. public Map<String, ObjectId> getHeadRev(String url) throws GitException, InterruptedException {
  704. Map<String, ObjectId> heads = new HashMap<String, ObjectId>();
  705. try {
  706. Repository repo = openDummyRepository();
  707. final Transport tn = Transport.open(repo, new URIish(url));
  708. tn.setCredentialsProvider(getProvider());
  709. final FetchConnection c = tn.openFetch();
  710. try {
  711. for (final Ref r : c.getRefs()) {
  712. heads.put(r.getName(), r.getPeeledObjectId() != null ? r.getPeeledObjectId() : r.getObjectId());
  713. }
  714. } finally {
  715. c.close();
  716. tn.close();
  717. repo.close();
  718. }
  719. } catch (IOException e) {
  720. throw new GitException(e);
  721. } catch (URISyntaxException e) {
  722. throw new GitException(e);
  723. }
  724. return heads;
  725. }
  726. /** {@inheritDoc} */
  727. public Map<String, ObjectId> getRemoteReferences(String url, String pattern, boolean headsOnly, boolean tagsOnly)
  728. throws GitException, InterruptedException {
  729. Map<String, ObjectId> references = new HashMap<String, ObjectId>();
  730. String regexPattern = null;
  731. if (pattern != null) {
  732. regexPattern = createRefRegexFromGlob(pattern);
  733. }
  734. try {
  735. Repository repo = openDummyRepository();
  736. LsRemoteCommand lsRemote = new LsRemoteCommand(repo);
  737. if (headsOnly) {
  738. lsRemote.setHeads(headsOnly);
  739. }
  740. if (tagsOnly) {
  741. lsRemote.setTags(tagsOnly);
  742. }
  743. lsRemote.setRemote(url);
  744. lsRemote.setCredentialsProvider(getProvider());
  745. Collection<Ref> refs = lsRemote.call();
  746. try {
  747. for (final Ref r : refs) {
  748. final String refName = r.getName();
  749. final ObjectId refObjectId =
  750. r.getPeeledObjectId() != null ? r.getPeeledObjectId() : r.getObjectId();
  751. if (regexPattern != null) {
  752. if (refName.matches(regexPattern)) {
  753. references.put(refName, refObjectId);
  754. }
  755. } else {
  756. references.put(refName, refObjectId);
  757. }
  758. }
  759. } finally {
  760. repo.close();
  761. }
  762. } catch (GitAPIException e) {
  763. throw new GitException(e);
  764. } catch (IOException e) {
  765. throw new GitException(e);
  766. }
  767. return references;
  768. }
  769. /* Adapted from http://stackoverflow.com/questions/1247772/is-there-an-equivalent-of-java-util-regex-for-glob-type-patterns */
  770. private String createRefRegexFromGlob(String glob)
  771. {
  772. StringBuilder out = new StringBuilder();
  773. if(glob.startsWith("refs/")) {
  774. out.append("^");
  775. } else {
  776. out.append("^.*/");
  777. }
  778. for (int i = 0; i < glob.length(); ++i) {
  779. final char c = glob.charAt(i);
  780. switch(c) {
  781. case '*':
  782. out.append(".*");
  783. break;
  784. case '?':
  785. out.append('.');
  786. break;
  787. case '.':
  788. out.append("\\.");
  789. break;
  790. case '\\':
  791. out.append("\\\\");
  792. break;
  793. default:
  794. out.append(c);
  795. break;
  796. }
  797. }
  798. out.append('$');
  799. return out.toString();
  800. }
  801. /** {@inheritDoc} */
  802. public ObjectId getHeadRev(String remoteRepoUrl, String branchSpec) throws GitException {
  803. try {
  804. final String branchName = extractBranchNameFromBranchSpec(branchSpec);
  805. String regexBranch = createRefRegexFromGlob(branchName);
  806. Repository repo = openDummyRepository();
  807. final Transport tn = Transport.open(repo, new URIish(remoteRepoUrl));
  808. tn.setCredentialsProvider(getProvider());
  809. final FetchConnection c = tn.openFetch();
  810. try {
  811. for (final Ref r : c.getRefs()) {
  812. if (r.getName().matches(regexBranch)) {
  813. return r.getPeeledObjectId() != null ? r.getPeeledObjectId() : r.getObjectId();
  814. }
  815. }
  816. } finally {
  817. c.close();
  818. tn.close();
  819. repo.close();
  820. }
  821. } catch (IOException e) {
  822. throw new GitException(e);
  823. } catch (URISyntaxException e) {
  824. throw new GitException(e);
  825. } catch (IllegalStateException e) {
  826. // "Cannot open session, connection is not authenticated." from com.trilead.ssh2.Connection.openSession
  827. throw new GitException(e);
  828. }
  829. return null;
  830. }
  831. /**
  832. * Creates a empty dummy {@link Repository} to keep JGit happy where it wants a valid {@link Repository} operation
  833. * for remote objects.
  834. */
  835. private Repository openDummyRepository() throws IOException {
  836. final File tempDir = Util.createTempDir();
  837. return new FileRepository(tempDir) {
  838. @Override
  839. public void close() {
  840. super.close();
  841. try {
  842. Util.deleteRecursive(tempDir);
  843. } catch (IOException e) {
  844. // ignore
  845. }
  846. }
  847. };
  848. }
  849. /** {@inheritDoc} */
  850. public String getRemoteUrl(String name) throws GitException {
  851. final Repository repo = getRepository();
  852. final String url = repo.getConfig().getString("remote",name,"url");
  853. repo.close();
  854. return url;
  855. }
  856. /**
  857. * getRepository.
  858. *
  859. * @return a {@link org.eclipse.jgit.lib.Repository} object.
  860. * @throws hudson.plugins.git.GitException if underlying git operation fails.
  861. */
  862. @NonNull
  863. public Repository getRepository() throws GitException {
  864. try {
  865. return new RepositoryBuilder().setWorkTree(workspace).build();
  866. } catch (IOException e) {
  867. throw new GitException(e);
  868. }
  869. }
  870. /**
  871. * getWorkTree.
  872. *
  873. * @return a {@link hudson.FilePath} object.
  874. */
  875. public FilePath getWorkTree() {
  876. return new FilePath(workspace);
  877. }
  878. /** {@inheritDoc} */
  879. public void setRemoteUrl(String name, String url) throws GitException {
  880. Repository repo = null;
  881. try {
  882. repo = getRepository();
  883. StoredConfig config = repo.getConfig();
  884. config.setString("remote", name, "url", url);
  885. config.save();
  886. } catch (IOException e) {
  887. throw new GitException(e);
  888. } finally {
  889. if (repo != null) repo.close();
  890. }
  891. }
  892. /** {@inheritDoc} */
  893. public void addRemoteUrl(String name, String url) throws GitException, InterruptedException {
  894. Repository repo = null;
  895. try {
  896. repo = getRepository();
  897. StoredConfig config = repo.getConfig();
  898. List<String> urls = new ArrayList<String>();
  899. urls.addAll(Arrays.asList(config.getStringList("remote", name, "url")));
  900. urls.add(url);
  901. config.setStringList("remote", name, "url", urls);
  902. config.save();
  903. } catch (IOException e) {
  904. throw new GitException(e);
  905. } finally {
  906. if (repo != null) repo.close();
  907. }
  908. }
  909. /** {@inheritDoc} */
  910. public void addNote(String note, String namespace) throws GitException {
  911. Repository repo = null;
  912. RevWalk walk = null;
  913. ObjectReader or = null;
  914. try {
  915. repo = getRepository();
  916. ObjectId head = repo.resolve(HEAD); // commit to put a note on
  917. AddNoteCommand cmd = git(repo).notesAdd();
  918. cmd.setMessage(normalizeNote(note));
  919. cmd.setNotesRef(qualifyNotesNamespace(namespace));
  920. or = repo.newObjectReader();
  921. walk = new RevWalk(or);
  922. cmd.setObjectId(walk.parseAny(head));
  923. cmd.call();
  924. } catch (GitAPIException e) {
  925. throw new GitException(e);
  926. } catch (IOException e) {
  927. throw new GitException(e);
  928. } finally {
  929. if (walk != null) walk.dispose();
  930. if (or != null) or.release();
  931. if (repo != null) repo.close();
  932. }
  933. }
  934. /**
  935. * Git-notes normalizes newlines.
  936. *
  937. * This behaviour is reverse engineered from limited experiments, so it may be incomplete.
  938. */
  939. private String normalizeNote(String note) {
  940. note = note.trim();
  941. note = note.replaceAll("\r\n","\n").replaceAll("\n{3,}","\n\n");
  942. note += "\n";
  943. return note;
  944. }
  945. private String qualifyNotesNamespace(String namespace) {
  946. if (!namespace.startsWith("refs/")) namespace = "refs/notes/"+namespace;
  947. return namespace;
  948. }
  949. /** {@inheritDoc} */
  950. public void appendNote(String note, String namespace) throws GitException {
  951. Repository repo = null;
  952. RevWalk walk = null;
  953. ObjectReader or = null;
  954. try {
  955. repo = getRepository();
  956. ObjectId head = repo.resolve(HEAD); // commit to put a note on
  957. ShowNoteCommand cmd = git(repo).notesShow();
  958. cmd.setNotesRef(qualifyNotesNamespace(namespace));
  959. or = repo.newObjectReader();
  960. walk = new RevWalk(or);
  961. cmd.setObjectId(walk.parseAny(head));
  962. Note n = cmd.call();
  963. if (n==null) {
  964. addNote(note,namespace);
  965. } else {
  966. ObjectLoader ol = or.open(n.getData());
  967. StringWriter sw = new StringWriter();
  968. IOUtils.copy(new InputStreamReader(ol.openStream(),CHARSET),sw);
  969. sw.write("\n");
  970. addNote(sw.toString() + normalizeNote(note), namespace);
  971. }
  972. } catch (GitAPIException e) {
  973. throw new GitException(e);
  974. } catch (IOException e) {
  975. throw new GitException(e);
  976. } finally {
  977. if (walk != null) walk.dispose();
  978. if (or != null) or.release();
  979. if (repo != null) repo.close();
  980. }
  981. }
  982. /**
  983. * changelog.
  984. *
  985. * @return a {@link org.jenkinsci.plugins.gitclient.ChangelogCommand} object.
  986. */
  987. public ChangelogCommand changelog() {
  988. return new ChangelogCommand() {
  989. Repository repo = getRepository();
  990. ObjectReader or = repo.newObjectReader();
  991. RevWalk walk = new RevWalk(or);
  992. Writer out;
  993. boolean hasIncludedRev = false;
  994. public ChangelogCommand excludes(String rev) {
  995. try {
  996. return excludes(repo.resolve(rev));
  997. } catch (IOException e) {
  998. throw new GitException(e);
  999. }
  1000. }
  1001. public ChangelogCommand excludes(ObjectId rev) {
  1002. try {
  1003. walk.markUninteresting(walk.lookupCommit(rev));
  1004. return this;
  1005. } catch (IOException e) {
  1006. throw new GitException(e);
  1007. }
  1008. }
  1009. public ChangelogCommand includes(String rev) {
  1010. try {
  1011. includes(repo.resolve(rev));
  1012. hasIncludedRev = true;
  1013. return this;
  1014. } catch (IOException e) {
  1015. throw new GitException(e);
  1016. }
  1017. }
  1018. public ChangelogCommand includes(ObjectId rev) {
  1019. try {
  1020. walk.markStart(walk.lookupCommit(rev));
  1021. hasIncludedRev = true;
  1022. return this;
  1023. } catch (IOException e) {
  1024. throw new GitException(e);
  1025. }
  1026. }
  1027. public ChangelogCommand to(Writer w) {
  1028. this.out = w;
  1029. return this;
  1030. }
  1031. public ChangelogCommand max(int n) {
  1032. walk.setRevFilter(MaxCountRevFilter.create(n));
  1033. return this;
  1034. }
  1035. private void closeResources() {
  1036. walk.dispose();
  1037. or.release();
  1038. repo.close();
  1039. }
  1040. public void abort() {
  1041. closeResources();
  1042. }
  1043. /** Execute the changelog command. Assumed that this is
  1044. * only performed once per instance of this object.
  1045. * Resources opened by this ChangelogCommand object are
  1046. * closed at exit from the execute method. Either execute
  1047. * or abort must be called for each ChangelogCommand or
  1048. * files will remain open.
  1049. */
  1050. public void execute() throws GitException, InterruptedException {
  1051. PrintWriter pw = new PrintWriter(out,false);
  1052. try {
  1053. RawFormatter formatter= new RawFormatter();
  1054. if (!hasIncludedRev) {
  1055. /* If no rev has been included, assume HEAD */
  1056. this.includes("HEAD");
  1057. }
  1058. for (RevCommit commit : walk) {
  1059. // git whatachanged doesn't show the merge commits unless -m is given
  1060. if (commit.getParentCount()>1) continue;
  1061. formatter.format(commit, null, pw, true);
  1062. }
  1063. } catch (IOException e) {
  1064. throw new GitException(e);
  1065. } finally {
  1066. closeResources();
  1067. pw.flush();
  1068. }
  1069. }
  1070. };
  1071. }
  1072. /**
  1073. * Formats {@link RevCommit}.
  1074. */
  1075. class RawFormatter {
  1076. private boolean hasNewPath(DiffEntry d) {
  1077. return d.getChangeType()==ChangeType.COPY || d.getChangeType()==ChangeType.RENAME;
  1078. }
  1079. private String statusOf(DiffEntry d) {
  1080. switch (d.getChangeType()) {
  1081. case ADD: return "A";
  1082. case MODIFY: return "M";
  1083. case DELETE: return "D";
  1084. case RENAME: return "R"+d.getScore();
  1085. case COPY: return "C"+d.getScore();
  1086. default:
  1087. throw new AssertionError("Unexpected change type: "+d.getChangeType());
  1088. }
  1089. }
  1090. public static final String ISO_8601 = "yyyy-MM-dd'T'HH:mm:ssZ";
  1091. /**
  1092. * Formats a commit into the raw format.
  1093. *
  1094. * @param commit
  1095. * Commit to format.
  1096. * @param parent
  1097. * Optional parent commit to produce the diff against. This only matters
  1098. * for merge commits, and git-log/git-whatchanged/etc behaves differently with respect to this.
  1099. */
  1100. void format(RevCommit commit, @Nullable RevCommit parent, PrintWriter pw, Boolean useRawOutput) throws IOException {
  1101. if (parent!=null)
  1102. pw.printf("commit %s (from %s)\n", commit.name(), parent.name());
  1103. else
  1104. pw.printf("commit %s\n", commit.name());
  1105. pw.printf("tree %s\n", commit.getTree().name());
  1106. for (RevCommit p : commit.getParents())
  1107. pw.printf("parent %s\n",p.name());
  1108. FastDateFormat iso = FastDateFormat.getInstance(ISO_8601);
  1109. PersonIdent a = commit.getAuthorIdent();
  1110. pw.printf("author %s <%s> %s\n", a.getName(), a.getEmailAddress(), iso.format(a.getWhen()));
  1111. PersonIdent c = commit.getCommitterIdent();
  1112. pw.printf("committer %s <%s> %s\n", c.getName(), c.getEmailAddress(), iso.format(c.getWhen()));
  1113. // indent commit messages by 4 chars
  1114. String msg = commit.getFullMessage();
  1115. if (msg.endsWith("\n")) msg=msg.substring(0,msg.length()-1);
  1116. msg = msg.replace("\n","\n ");
  1117. msg="\n "+msg+"\n";
  1118. pw.println(msg);
  1119. // see man git-diff-tree for the format
  1120. Repository repo = getRepository();
  1121. ObjectReader or = repo.newObjectReader();
  1122. TreeWalk tw = new TreeWalk(or);
  1123. if (parent != null) {
  1124. /* Caller provided a parent commit, use it */
  1125. tw.reset(parent.getTree(), commit.getTree());
  1126. } else {
  1127. if (commit.getParentCount() > 0) {
  1128. /* Caller failed to provide parent, but a parent
  1129. * is available, so use the parent in the walk
  1130. */
  1131. tw.reset(commit.getParent(0).getTree(), commit.getTree());
  1132. } else {
  1133. /* First commit in repo has 0 parent count, but
  1134. * the TreeWalk requires exactly two nodes for its
  1135. * walk. Use the same node twice to satisfy
  1136. * TreeWalk. See JENKINS-22343 for details.
  1137. */
  1138. tw.reset(commit.getTree(), commit.getTree());
  1139. }
  1140. }
  1141. tw.setRecursive(true);
  1142. tw.setFilter(TreeFilter.ANY_DIFF);
  1143. final RenameDetector rd = new RenameDetector(repo);
  1144. rd.reset();
  1145. rd.addAll(DiffEntry.scan(tw));
  1146. List<DiffEntry> diffs = rd.compute(or, null);
  1147. tw.release();
  1148. or.release();
  1149. repo.close();
  1150. if (useRawOutput) {
  1151. for (DiffEntry diff : diffs) {
  1152. pw.printf(":%06o %06o %s %s %s\t%s",
  1153. diff.getOldMode().getBits(),
  1154. diff.getNewMode().getBits(),
  1155. diff.getOldId().name(),
  1156. diff.getNewId().name(),
  1157. statusOf(diff),
  1158. diff.getChangeType()==ChangeType.ADD ? diff.getNewPath() : diff.getOldPath());
  1159. if (hasNewPath(diff)) {
  1160. pw.printf(" %s",diff.getNewPath()); // copied to
  1161. }
  1162. pw.println();
  1163. pw.println();
  1164. }
  1165. }
  1166. }
  1167. }
  1168. /**
  1169. * clean.
  1170. *
  1171. * @throws hudson.plugins.git.GitException if underlying git operation fails.
  1172. */
  1173. public void clean() throws GitException {
  1174. Repository repo = null;
  1175. try {
  1176. repo = getRepository();
  1177. Git git = git(repo);
  1178. git.reset().setMode(HARD).call();
  1179. git.clean().setCleanDirectories(true).setIgnore(false).call();
  1180. } catch (GitAPIException e) {
  1181. throw new GitException(e);
  1182. } finally {
  1183. if (repo != null) repo.close();
  1184. }
  1185. }
  1186. /**
  1187. * clone_.
  1188. *
  1189. * @return a {@link org.jenkinsci.plugins.gitclient.CloneCommand} object.
  1190. */
  1191. public CloneCommand clone_() {
  1192. return new CloneCommand() {
  1193. String url;
  1194. String remote = Constants.DEFAULT_REMOTE_NAME;
  1195. String reference;
  1196. Integer timeout;
  1197. boolean shared;
  1198. boolean tags = true;
  1199. List<RefSpec> refspecs;
  1200. public CloneCommand url(String url) {
  1201. this.url = url;
  1202. return this;
  1203. }
  1204. public CloneCommand repositoryName(String name) {
  1205. this.remote = name;
  1206. return this;
  1207. }
  1208. public CloneCommand shallow() {
  1209. listener.getLogger().println("[WARNING] JGit doesn't support shallow clone. This flag is ignored");
  1210. return this;
  1211. }
  1212. public CloneCommand shared() {
  1213. this.shared = true;
  1214. return this;
  1215. }
  1216. public CloneCommand reference(String reference) {
  1217. this.reference = reference;
  1218. return this;
  1219. }
  1220. public CloneCommand refspecs(List<RefSpec> refspecs) {
  1221. this.refspecs = new ArrayList<RefSpec>(refspecs);
  1222. return this;
  1223. }
  1224. public CloneCommand timeout(Integer timeout) {
  1225. this.timeout = timeout;
  1226. return this;
  1227. }