You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RepoCommand.java 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887
  1. /*
  2. * Copyright (C) 2014, 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. package org.eclipse.jgit.gitrepo;
  11. import static java.nio.charset.StandardCharsets.UTF_8;
  12. import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME;
  13. import static org.eclipse.jgit.lib.Constants.R_REMOTES;
  14. import java.io.File;
  15. import java.io.FileInputStream;
  16. import java.io.IOException;
  17. import java.io.InputStream;
  18. import java.net.URI;
  19. import java.text.MessageFormat;
  20. import java.util.ArrayList;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.Objects;
  24. import java.util.StringJoiner;
  25. import java.util.TreeMap;
  26. import org.eclipse.jgit.annotations.NonNull;
  27. import org.eclipse.jgit.annotations.Nullable;
  28. import org.eclipse.jgit.api.Git;
  29. import org.eclipse.jgit.api.GitCommand;
  30. import org.eclipse.jgit.api.SubmoduleAddCommand;
  31. import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
  32. import org.eclipse.jgit.api.errors.GitAPIException;
  33. import org.eclipse.jgit.api.errors.InvalidRefNameException;
  34. import org.eclipse.jgit.api.errors.JGitInternalException;
  35. import org.eclipse.jgit.dircache.DirCache;
  36. import org.eclipse.jgit.dircache.DirCacheBuilder;
  37. import org.eclipse.jgit.dircache.DirCacheEntry;
  38. import org.eclipse.jgit.gitrepo.ManifestParser.IncludedFileReader;
  39. import org.eclipse.jgit.gitrepo.RepoProject.CopyFile;
  40. import org.eclipse.jgit.gitrepo.RepoProject.LinkFile;
  41. import org.eclipse.jgit.gitrepo.internal.RepoText;
  42. import org.eclipse.jgit.internal.JGitText;
  43. import org.eclipse.jgit.lib.CommitBuilder;
  44. import org.eclipse.jgit.lib.Config;
  45. import org.eclipse.jgit.lib.Constants;
  46. import org.eclipse.jgit.lib.FileMode;
  47. import org.eclipse.jgit.lib.ObjectId;
  48. import org.eclipse.jgit.lib.ObjectInserter;
  49. import org.eclipse.jgit.lib.PersonIdent;
  50. import org.eclipse.jgit.lib.ProgressMonitor;
  51. import org.eclipse.jgit.lib.Ref;
  52. import org.eclipse.jgit.lib.RefDatabase;
  53. import org.eclipse.jgit.lib.RefUpdate;
  54. import org.eclipse.jgit.lib.RefUpdate.Result;
  55. import org.eclipse.jgit.lib.Repository;
  56. import org.eclipse.jgit.revwalk.RevCommit;
  57. import org.eclipse.jgit.revwalk.RevWalk;
  58. import org.eclipse.jgit.treewalk.TreeWalk;
  59. import org.eclipse.jgit.util.FileUtils;
  60. /**
  61. * A class used to execute a repo command.
  62. *
  63. * This will parse a repo XML manifest, convert it into .gitmodules file and the
  64. * repository config file.
  65. *
  66. * If called against a bare repository, it will replace all the existing content
  67. * of the repository with the contents populated from the manifest.
  68. *
  69. * repo manifest allows projects overlapping, e.g. one project's manifestPath is
  70. * "foo" and another project's manifestPath is "foo/bar". This won't
  71. * work in git submodule, so we'll skip all the sub projects
  72. * ("foo/bar" in the example) while converting.
  73. *
  74. * @see <a href="https://code.google.com/p/git-repo/">git-repo project page</a>
  75. * @since 3.4
  76. */
  77. public class RepoCommand extends GitCommand<RevCommit> {
  78. private String manifestPath;
  79. private String baseUri;
  80. private URI targetUri;
  81. private String groupsParam;
  82. private String branch;
  83. private String targetBranch = Constants.HEAD;
  84. private boolean recordRemoteBranch = true;
  85. private boolean recordSubmoduleLabels = true;
  86. private boolean recordShallowSubmodules = true;
  87. private PersonIdent author;
  88. private RemoteReader callback;
  89. private InputStream inputStream;
  90. private IncludedFileReader includedReader;
  91. private boolean ignoreRemoteFailures = false;
  92. private ProgressMonitor monitor;
  93. /**
  94. * A callback to get ref sha1 of a repository from its uri.
  95. *
  96. * We provided a default implementation {@link DefaultRemoteReader} to
  97. * use ls-remote command to read the sha1 from the repository and clone the
  98. * repository to read the file. Callers may have their own quicker
  99. * implementation.
  100. *
  101. * @since 3.4
  102. */
  103. public interface RemoteReader {
  104. /**
  105. * Read a remote ref sha1.
  106. *
  107. * @param uri
  108. * The URI of the remote repository
  109. * @param ref
  110. * Name of the ref to lookup. May be a short-hand form, e.g.
  111. * "master" which is automatically expanded to
  112. * "refs/heads/master" if "refs/heads/master" already exists.
  113. * @return the sha1 of the remote repository, or null if the ref does
  114. * not exist.
  115. * @throws GitAPIException
  116. */
  117. @Nullable
  118. public ObjectId sha1(String uri, String ref) throws GitAPIException;
  119. /**
  120. * Read a file from a remote repository.
  121. *
  122. * @param uri
  123. * The URI of the remote repository
  124. * @param ref
  125. * The ref (branch/tag/etc.) to read
  126. * @param path
  127. * The relative path (inside the repo) to the file to read
  128. * @return the file content.
  129. * @throws GitAPIException
  130. * @throws IOException
  131. * @since 3.5
  132. *
  133. * @deprecated Use {@link #readFileWithMode(String, String, String)}
  134. * instead
  135. */
  136. @Deprecated
  137. public default byte[] readFile(String uri, String ref, String path)
  138. throws GitAPIException, IOException {
  139. return readFileWithMode(uri, ref, path).getContents();
  140. }
  141. /**
  142. * Read contents and mode (i.e. permissions) of the file from a remote
  143. * repository.
  144. *
  145. * @param uri
  146. * The URI of the remote repository
  147. * @param ref
  148. * Name of the ref to lookup. May be a short-hand form, e.g.
  149. * "master" which is automatically expanded to
  150. * "refs/heads/master" if "refs/heads/master" already exists.
  151. * @param path
  152. * The relative path (inside the repo) to the file to read
  153. * @return The contents and file mode of the file in the given
  154. * repository and branch. Never null.
  155. * @throws GitAPIException
  156. * If the ref have an invalid or ambiguous name, or it does
  157. * not exist in the repository,
  158. * @throws IOException
  159. * If the object does not exist or is too large
  160. * @since 5.2
  161. */
  162. @NonNull
  163. public RemoteFile readFileWithMode(String uri, String ref, String path)
  164. throws GitAPIException, IOException;
  165. }
  166. /**
  167. * Read-only view of contents and file mode (i.e. permissions) for a file in
  168. * a remote repository.
  169. *
  170. * @since 5.2
  171. */
  172. public static final class RemoteFile {
  173. @NonNull
  174. private final byte[] contents;
  175. @NonNull
  176. private final FileMode fileMode;
  177. /**
  178. * @param contents
  179. * Raw contents of the file.
  180. * @param fileMode
  181. * Git file mode for this file (e.g. executable or regular)
  182. */
  183. public RemoteFile(@NonNull byte[] contents,
  184. @NonNull FileMode fileMode) {
  185. this.contents = Objects.requireNonNull(contents);
  186. this.fileMode = Objects.requireNonNull(fileMode);
  187. }
  188. /**
  189. * Contents of the file.
  190. * <p>
  191. * Callers who receive this reference must not modify its contents (as
  192. * it can point to internal cached data).
  193. *
  194. * @return Raw contents of the file. Do not modify it.
  195. */
  196. @NonNull
  197. public byte[] getContents() {
  198. return contents;
  199. }
  200. /**
  201. * @return Git file mode for this file (e.g. executable or regular)
  202. */
  203. @NonNull
  204. public FileMode getFileMode() {
  205. return fileMode;
  206. }
  207. }
  208. /** A default implementation of {@link RemoteReader} callback. */
  209. public static class DefaultRemoteReader implements RemoteReader {
  210. @Override
  211. public ObjectId sha1(String uri, String ref) throws GitAPIException {
  212. Map<String, Ref> map = Git
  213. .lsRemoteRepository()
  214. .setRemote(uri)
  215. .callAsMap();
  216. Ref r = RefDatabase.findRef(map, ref);
  217. return r != null ? r.getObjectId() : null;
  218. }
  219. @Override
  220. public RemoteFile readFileWithMode(String uri, String ref, String path)
  221. throws GitAPIException, IOException {
  222. File dir = FileUtils.createTempDir("jgit_", ".git", null); //$NON-NLS-1$ //$NON-NLS-2$
  223. try (Git git = Git.cloneRepository().setBare(true).setDirectory(dir)
  224. .setURI(uri).call()) {
  225. Repository repo = git.getRepository();
  226. ObjectId refCommitId = sha1(uri, ref);
  227. if (refCommitId == null) {
  228. throw new InvalidRefNameException(MessageFormat
  229. .format(JGitText.get().refNotResolved, ref));
  230. }
  231. RevCommit commit = repo.parseCommit(refCommitId);
  232. TreeWalk tw = TreeWalk.forPath(repo, path, commit.getTree());
  233. // TODO(ifrade): Cope better with big files (e.g. using
  234. // InputStream instead of byte[])
  235. return new RemoteFile(
  236. tw.getObjectReader().open(tw.getObjectId(0))
  237. .getCachedBytes(Integer.MAX_VALUE),
  238. tw.getFileMode(0));
  239. } finally {
  240. FileUtils.delete(dir, FileUtils.RECURSIVE);
  241. }
  242. }
  243. }
  244. @SuppressWarnings("serial")
  245. private static class ManifestErrorException extends GitAPIException {
  246. ManifestErrorException(Throwable cause) {
  247. super(RepoText.get().invalidManifest, cause);
  248. }
  249. }
  250. @SuppressWarnings("serial")
  251. private static class RemoteUnavailableException extends GitAPIException {
  252. RemoteUnavailableException(String uri) {
  253. super(MessageFormat.format(RepoText.get().errorRemoteUnavailable, uri));
  254. }
  255. }
  256. /**
  257. * Constructor for RepoCommand
  258. *
  259. * @param repo
  260. * the {@link org.eclipse.jgit.lib.Repository}
  261. */
  262. public RepoCommand(Repository repo) {
  263. super(repo);
  264. }
  265. /**
  266. * Set path to the manifest XML file.
  267. * <p>
  268. * Calling {@link #setInputStream} will ignore the path set here.
  269. *
  270. * @param path
  271. * (with <code>/</code> as separator)
  272. * @return this command
  273. */
  274. public RepoCommand setPath(String path) {
  275. this.manifestPath = path;
  276. return this;
  277. }
  278. /**
  279. * Set the input stream to the manifest XML.
  280. * <p>
  281. * Setting inputStream will ignore the path set. It will be closed in
  282. * {@link #call}.
  283. *
  284. * @param inputStream a {@link java.io.InputStream} object.
  285. * @return this command
  286. * @since 3.5
  287. */
  288. public RepoCommand setInputStream(InputStream inputStream) {
  289. this.inputStream = inputStream;
  290. return this;
  291. }
  292. /**
  293. * Set base URI of the paths inside the XML. This is typically the name of
  294. * the directory holding the manifest repository, eg. for
  295. * https://android.googlesource.com/platform/manifest, this should be
  296. * /platform (if you would run this on android.googlesource.com) or
  297. * https://android.googlesource.com/platform elsewhere.
  298. *
  299. * @param uri
  300. * the base URI
  301. * @return this command
  302. */
  303. public RepoCommand setURI(String uri) {
  304. this.baseUri = uri;
  305. return this;
  306. }
  307. /**
  308. * Set the URI of the superproject (this repository), so the .gitmodules
  309. * file can specify the submodule URLs relative to the superproject.
  310. *
  311. * @param uri
  312. * the URI of the repository holding the superproject.
  313. * @return this command
  314. * @since 4.8
  315. */
  316. public RepoCommand setTargetURI(String uri) {
  317. // The repo name is interpreted as a directory, for example
  318. // Gerrit (http://gerrit.googlesource.com/gerrit) has a
  319. // .gitmodules referencing ../plugins/hooks, which is
  320. // on http://gerrit.googlesource.com/plugins/hooks,
  321. this.targetUri = URI.create(uri + "/"); //$NON-NLS-1$
  322. return this;
  323. }
  324. /**
  325. * Set groups to sync
  326. *
  327. * @param groups groups separated by comma, examples: default|all|G1,-G2,-G3
  328. * @return this command
  329. */
  330. public RepoCommand setGroups(String groups) {
  331. this.groupsParam = groups;
  332. return this;
  333. }
  334. /**
  335. * Set default branch.
  336. * <p>
  337. * This is generally the name of the branch the manifest file was in. If
  338. * there's no default revision (branch) specified in manifest and no
  339. * revision specified in project, this branch will be used.
  340. *
  341. * @param branch
  342. * a branch name
  343. * @return this command
  344. */
  345. public RepoCommand setBranch(String branch) {
  346. this.branch = branch;
  347. return this;
  348. }
  349. /**
  350. * Set target branch.
  351. * <p>
  352. * This is the target branch of the super project to be updated. If not set,
  353. * default is HEAD.
  354. * <p>
  355. * For non-bare repositories, HEAD will always be used and this will be
  356. * ignored.
  357. *
  358. * @param branch
  359. * branch name
  360. * @return this command
  361. * @since 4.1
  362. */
  363. public RepoCommand setTargetBranch(String branch) {
  364. this.targetBranch = Constants.R_HEADS + branch;
  365. return this;
  366. }
  367. /**
  368. * Set whether the branch name should be recorded in .gitmodules.
  369. * <p>
  370. * Submodule entries in .gitmodules can include a "branch" field
  371. * to indicate what remote branch each submodule tracks.
  372. * <p>
  373. * That field is used by "git submodule update --remote" to update
  374. * to the tip of the tracked branch when asked and by Gerrit to
  375. * update the superproject when a change on that branch is merged.
  376. * <p>
  377. * Subprojects that request a specific commit or tag will not have
  378. * a branch name recorded.
  379. * <p>
  380. * Not implemented for non-bare repositories.
  381. *
  382. * @param enable Whether to record the branch name
  383. * @return this command
  384. * @since 4.2
  385. */
  386. public RepoCommand setRecordRemoteBranch(boolean enable) {
  387. this.recordRemoteBranch = enable;
  388. return this;
  389. }
  390. /**
  391. * Set whether the labels field should be recorded as a label in
  392. * .gitattributes.
  393. * <p>
  394. * Not implemented for non-bare repositories.
  395. *
  396. * @param enable Whether to record the labels in the .gitattributes
  397. * @return this command
  398. * @since 4.4
  399. */
  400. public RepoCommand setRecordSubmoduleLabels(boolean enable) {
  401. this.recordSubmoduleLabels = enable;
  402. return this;
  403. }
  404. /**
  405. * Set whether the clone-depth field should be recorded as a shallow
  406. * recommendation in .gitmodules.
  407. * <p>
  408. * Not implemented for non-bare repositories.
  409. *
  410. * @param enable Whether to record the shallow recommendation.
  411. * @return this command
  412. * @since 4.4
  413. */
  414. public RepoCommand setRecommendShallow(boolean enable) {
  415. this.recordShallowSubmodules = enable;
  416. return this;
  417. }
  418. /**
  419. * The progress monitor associated with the clone operation. By default,
  420. * this is set to <code>NullProgressMonitor</code>
  421. *
  422. * @see org.eclipse.jgit.lib.NullProgressMonitor
  423. * @param monitor
  424. * a {@link org.eclipse.jgit.lib.ProgressMonitor}
  425. * @return this command
  426. */
  427. public RepoCommand setProgressMonitor(ProgressMonitor monitor) {
  428. this.monitor = monitor;
  429. return this;
  430. }
  431. /**
  432. * Set whether to skip projects whose commits don't exist remotely.
  433. * <p>
  434. * When set to true, we'll just skip the manifest entry and continue
  435. * on to the next one.
  436. * <p>
  437. * When set to false (default), we'll throw an error when remote
  438. * failures occur.
  439. * <p>
  440. * Not implemented for non-bare repositories.
  441. *
  442. * @param ignore Whether to ignore the remote failures.
  443. * @return this command
  444. * @since 4.3
  445. */
  446. public RepoCommand setIgnoreRemoteFailures(boolean ignore) {
  447. this.ignoreRemoteFailures = ignore;
  448. return this;
  449. }
  450. /**
  451. * Set the author/committer for the bare repository commit.
  452. * <p>
  453. * For non-bare repositories, the current user will be used and this will be
  454. * ignored.
  455. *
  456. * @param author
  457. * the author's {@link org.eclipse.jgit.lib.PersonIdent}
  458. * @return this command
  459. */
  460. public RepoCommand setAuthor(PersonIdent author) {
  461. this.author = author;
  462. return this;
  463. }
  464. /**
  465. * Set the GetHeadFromUri callback.
  466. *
  467. * This is only used in bare repositories.
  468. *
  469. * @param callback
  470. * a {@link org.eclipse.jgit.gitrepo.RepoCommand.RemoteReader}
  471. * object.
  472. * @return this command
  473. */
  474. public RepoCommand setRemoteReader(RemoteReader callback) {
  475. this.callback = callback;
  476. return this;
  477. }
  478. /**
  479. * Set the IncludedFileReader callback.
  480. *
  481. * @param reader
  482. * a
  483. * {@link org.eclipse.jgit.gitrepo.ManifestParser.IncludedFileReader}
  484. * object.
  485. * @return this command
  486. * @since 4.0
  487. */
  488. public RepoCommand setIncludedFileReader(IncludedFileReader reader) {
  489. this.includedReader = reader;
  490. return this;
  491. }
  492. /** {@inheritDoc} */
  493. @Override
  494. public RevCommit call() throws GitAPIException {
  495. checkCallable();
  496. if (baseUri == null) {
  497. baseUri = ""; //$NON-NLS-1$
  498. }
  499. if (inputStream == null) {
  500. if (manifestPath == null || manifestPath.length() == 0)
  501. throw new IllegalArgumentException(
  502. JGitText.get().pathNotConfigured);
  503. try {
  504. inputStream = new FileInputStream(manifestPath);
  505. } catch (IOException e) {
  506. throw new IllegalArgumentException(
  507. JGitText.get().pathNotConfigured, e);
  508. }
  509. }
  510. List<RepoProject> filteredProjects;
  511. try {
  512. ManifestParser parser = new ManifestParser(includedReader,
  513. manifestPath, branch, baseUri, groupsParam, repo);
  514. parser.read(inputStream);
  515. filteredProjects = parser.getFilteredProjects();
  516. } catch (IOException e) {
  517. throw new ManifestErrorException(e);
  518. } finally {
  519. try {
  520. inputStream.close();
  521. } catch (IOException e) {
  522. // Just ignore it, it's not important.
  523. }
  524. }
  525. if (repo.isBare()) {
  526. if (author == null)
  527. author = new PersonIdent(repo);
  528. if (callback == null)
  529. callback = new DefaultRemoteReader();
  530. List<RepoProject> renamedProjects = renameProjects(filteredProjects);
  531. DirCache index = DirCache.newInCore();
  532. DirCacheBuilder builder = index.builder();
  533. ObjectInserter inserter = repo.newObjectInserter();
  534. try (RevWalk rw = new RevWalk(repo)) {
  535. Config cfg = new Config();
  536. StringBuilder attributes = new StringBuilder();
  537. for (RepoProject proj : renamedProjects) {
  538. String name = proj.getName();
  539. String path = proj.getPath();
  540. String url = proj.getUrl();
  541. ObjectId objectId;
  542. if (ObjectId.isId(proj.getRevision())) {
  543. objectId = ObjectId.fromString(proj.getRevision());
  544. } else {
  545. objectId = callback.sha1(url, proj.getRevision());
  546. if (objectId == null && !ignoreRemoteFailures) {
  547. throw new RemoteUnavailableException(url);
  548. }
  549. if (recordRemoteBranch) {
  550. // can be branch or tag
  551. cfg.setString("submodule", name, "branch", //$NON-NLS-1$ //$NON-NLS-2$
  552. proj.getRevision());
  553. }
  554. if (recordShallowSubmodules && proj.getRecommendShallow() != null) {
  555. // The shallow recommendation is losing information.
  556. // As the repo manifests stores the recommended
  557. // depth in the 'clone-depth' field, while
  558. // git core only uses a binary 'shallow = true/false'
  559. // hint, we'll map any depth to 'shallow = true'
  560. cfg.setBoolean("submodule", name, "shallow", //$NON-NLS-1$ //$NON-NLS-2$
  561. true);
  562. }
  563. }
  564. if (recordSubmoduleLabels) {
  565. StringBuilder rec = new StringBuilder();
  566. rec.append("/"); //$NON-NLS-1$
  567. rec.append(path);
  568. for (String group : proj.getGroups()) {
  569. rec.append(" "); //$NON-NLS-1$
  570. rec.append(group);
  571. }
  572. rec.append("\n"); //$NON-NLS-1$
  573. attributes.append(rec.toString());
  574. }
  575. URI submodUrl = URI.create(url);
  576. if (targetUri != null) {
  577. submodUrl = relativize(targetUri, submodUrl);
  578. }
  579. cfg.setString("submodule", name, "path", path); //$NON-NLS-1$ //$NON-NLS-2$
  580. cfg.setString("submodule", name, "url", //$NON-NLS-1$ //$NON-NLS-2$
  581. submodUrl.toString());
  582. // create gitlink
  583. if (objectId != null) {
  584. DirCacheEntry dcEntry = new DirCacheEntry(path);
  585. dcEntry.setObjectId(objectId);
  586. dcEntry.setFileMode(FileMode.GITLINK);
  587. builder.add(dcEntry);
  588. for (CopyFile copyfile : proj.getCopyFiles()) {
  589. RemoteFile rf = callback.readFileWithMode(
  590. url, proj.getRevision(), copyfile.src);
  591. objectId = inserter.insert(Constants.OBJ_BLOB,
  592. rf.getContents());
  593. dcEntry = new DirCacheEntry(copyfile.dest);
  594. dcEntry.setObjectId(objectId);
  595. dcEntry.setFileMode(rf.getFileMode());
  596. builder.add(dcEntry);
  597. }
  598. for (LinkFile linkfile : proj.getLinkFiles()) {
  599. String link;
  600. if (linkfile.dest.contains("/")) { //$NON-NLS-1$
  601. link = FileUtils.relativizeGitPath(
  602. linkfile.dest.substring(0,
  603. linkfile.dest.lastIndexOf('/')),
  604. proj.getPath() + "/" + linkfile.src); //$NON-NLS-1$
  605. } else {
  606. link = proj.getPath() + "/" + linkfile.src; //$NON-NLS-1$
  607. }
  608. objectId = inserter.insert(Constants.OBJ_BLOB,
  609. link.getBytes(UTF_8));
  610. dcEntry = new DirCacheEntry(linkfile.dest);
  611. dcEntry.setObjectId(objectId);
  612. dcEntry.setFileMode(FileMode.SYMLINK);
  613. builder.add(dcEntry);
  614. }
  615. }
  616. }
  617. String content = cfg.toText();
  618. // create a new DirCacheEntry for .gitmodules file.
  619. final DirCacheEntry dcEntry = new DirCacheEntry(Constants.DOT_GIT_MODULES);
  620. ObjectId objectId = inserter.insert(Constants.OBJ_BLOB,
  621. content.getBytes(UTF_8));
  622. dcEntry.setObjectId(objectId);
  623. dcEntry.setFileMode(FileMode.REGULAR_FILE);
  624. builder.add(dcEntry);
  625. if (recordSubmoduleLabels) {
  626. // create a new DirCacheEntry for .gitattributes file.
  627. final DirCacheEntry dcEntryAttr = new DirCacheEntry(Constants.DOT_GIT_ATTRIBUTES);
  628. ObjectId attrId = inserter.insert(Constants.OBJ_BLOB,
  629. attributes.toString().getBytes(UTF_8));
  630. dcEntryAttr.setObjectId(attrId);
  631. dcEntryAttr.setFileMode(FileMode.REGULAR_FILE);
  632. builder.add(dcEntryAttr);
  633. }
  634. builder.finish();
  635. ObjectId treeId = index.writeTree(inserter);
  636. // Create a Commit object, populate it and write it
  637. ObjectId headId = repo.resolve(targetBranch + "^{commit}"); //$NON-NLS-1$
  638. if (headId != null && rw.parseCommit(headId).getTree().getId().equals(treeId)) {
  639. // No change. Do nothing.
  640. return rw.parseCommit(headId);
  641. }
  642. CommitBuilder commit = new CommitBuilder();
  643. commit.setTreeId(treeId);
  644. if (headId != null)
  645. commit.setParentIds(headId);
  646. commit.setAuthor(author);
  647. commit.setCommitter(author);
  648. commit.setMessage(RepoText.get().repoCommitMessage);
  649. ObjectId commitId = inserter.insert(commit);
  650. inserter.flush();
  651. RefUpdate ru = repo.updateRef(targetBranch);
  652. ru.setNewObjectId(commitId);
  653. ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId());
  654. Result rc = ru.update(rw);
  655. switch (rc) {
  656. case NEW:
  657. case FORCED:
  658. case FAST_FORWARD:
  659. // Successful. Do nothing.
  660. break;
  661. case REJECTED:
  662. case LOCK_FAILURE:
  663. throw new ConcurrentRefUpdateException(
  664. MessageFormat.format(
  665. JGitText.get().cannotLock, targetBranch),
  666. ru.getRef(),
  667. rc);
  668. default:
  669. throw new JGitInternalException(MessageFormat.format(
  670. JGitText.get().updatingRefFailed,
  671. targetBranch, commitId.name(), rc));
  672. }
  673. return rw.parseCommit(commitId);
  674. } catch (GitAPIException | IOException e) {
  675. throw new ManifestErrorException(e);
  676. }
  677. }
  678. try (Git git = new Git(repo)) {
  679. for (RepoProject proj : filteredProjects) {
  680. addSubmodule(proj.getName(), proj.getUrl(), proj.getPath(),
  681. proj.getRevision(), proj.getCopyFiles(),
  682. proj.getLinkFiles(), git);
  683. }
  684. return git.commit().setMessage(RepoText.get().repoCommitMessage)
  685. .call();
  686. } catch (GitAPIException | IOException e) {
  687. throw new ManifestErrorException(e);
  688. }
  689. }
  690. private void addSubmodule(String name, String url, String path,
  691. String revision, List<CopyFile> copyfiles, List<LinkFile> linkfiles,
  692. Git git) throws GitAPIException, IOException {
  693. assert (!repo.isBare());
  694. assert (git != null);
  695. if (!linkfiles.isEmpty()) {
  696. throw new UnsupportedOperationException(
  697. JGitText.get().nonBareLinkFilesNotSupported);
  698. }
  699. SubmoduleAddCommand add = git.submoduleAdd().setName(name).setPath(path)
  700. .setURI(url);
  701. if (monitor != null)
  702. add.setProgressMonitor(monitor);
  703. Repository subRepo = add.call();
  704. if (revision != null) {
  705. try (Git sub = new Git(subRepo)) {
  706. sub.checkout().setName(findRef(revision, subRepo)).call();
  707. }
  708. subRepo.close();
  709. git.add().addFilepattern(path).call();
  710. }
  711. for (CopyFile copyfile : copyfiles) {
  712. copyfile.copy();
  713. git.add().addFilepattern(copyfile.dest).call();
  714. }
  715. }
  716. /**
  717. * Rename the projects if there's a conflict when converted to submodules.
  718. *
  719. * @param projects
  720. * parsed projects
  721. * @return projects that are renamed if necessary
  722. */
  723. private List<RepoProject> renameProjects(List<RepoProject> projects) {
  724. Map<String, List<RepoProject>> m = new TreeMap<>();
  725. for (RepoProject proj : projects) {
  726. List<RepoProject> l = m.get(proj.getName());
  727. if (l == null) {
  728. l = new ArrayList<>();
  729. m.put(proj.getName(), l);
  730. }
  731. l.add(proj);
  732. }
  733. List<RepoProject> ret = new ArrayList<>();
  734. for (List<RepoProject> ps : m.values()) {
  735. boolean nameConflict = ps.size() != 1;
  736. for (RepoProject proj : ps) {
  737. String name = proj.getName();
  738. if (nameConflict) {
  739. name += SLASH + proj.getPath();
  740. }
  741. RepoProject p = new RepoProject(name,
  742. proj.getPath(), proj.getRevision(), null,
  743. proj.getGroups(), proj.getRecommendShallow());
  744. p.setUrl(proj.getUrl());
  745. p.addCopyFiles(proj.getCopyFiles());
  746. p.addLinkFiles(proj.getLinkFiles());
  747. ret.add(p);
  748. }
  749. }
  750. return ret;
  751. }
  752. /*
  753. * Assume we are document "a/b/index.html", what should we put in a href to get to "a/" ?
  754. * Returns the child if either base or child is not a bare path. This provides a missing feature in
  755. * java.net.URI (see http://bugs.java.com/view_bug.do?bug_id=6226081).
  756. */
  757. private static final String SLASH = "/"; //$NON-NLS-1$
  758. static URI relativize(URI current, URI target) {
  759. if (!Objects.equals(current.getHost(), target.getHost())) {
  760. return target;
  761. }
  762. String cur = current.normalize().getPath();
  763. String dest = target.normalize().getPath();
  764. // TODO(hanwen): maybe (absolute, relative) should throw an exception.
  765. if (cur.startsWith(SLASH) != dest.startsWith(SLASH)) {
  766. return target;
  767. }
  768. while (cur.startsWith(SLASH)) {
  769. cur = cur.substring(1);
  770. }
  771. while (dest.startsWith(SLASH)) {
  772. dest = dest.substring(1);
  773. }
  774. if (cur.indexOf('/') == -1 || dest.indexOf('/') == -1) {
  775. // Avoid having to special-casing in the next two ifs.
  776. String prefix = "prefix/"; //$NON-NLS-1$
  777. cur = prefix + cur;
  778. dest = prefix + dest;
  779. }
  780. if (!cur.endsWith(SLASH)) {
  781. // The current file doesn't matter.
  782. int lastSlash = cur.lastIndexOf('/');
  783. cur = cur.substring(0, lastSlash);
  784. }
  785. String destFile = ""; //$NON-NLS-1$
  786. if (!dest.endsWith(SLASH)) {
  787. // We always have to provide the destination file.
  788. int lastSlash = dest.lastIndexOf('/');
  789. destFile = dest.substring(lastSlash + 1, dest.length());
  790. dest = dest.substring(0, dest.lastIndexOf('/'));
  791. }
  792. String[] cs = cur.split(SLASH);
  793. String[] ds = dest.split(SLASH);
  794. int common = 0;
  795. while (common < cs.length && common < ds.length && cs[common].equals(ds[common])) {
  796. common++;
  797. }
  798. StringJoiner j = new StringJoiner(SLASH);
  799. for (int i = common; i < cs.length; i++) {
  800. j.add(".."); //$NON-NLS-1$
  801. }
  802. for (int i = common; i < ds.length; i++) {
  803. j.add(ds[i]);
  804. }
  805. j.add(destFile);
  806. return URI.create(j.toString());
  807. }
  808. private static String findRef(String ref, Repository repo)
  809. throws IOException {
  810. if (!ObjectId.isId(ref)) {
  811. Ref r = repo.exactRef(R_REMOTES + DEFAULT_REMOTE_NAME + "/" + ref); //$NON-NLS-1$
  812. if (r != null)
  813. return r.getName();
  814. }
  815. return ref;
  816. }
  817. }