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.

Repository.java 38KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326
  1. /*
  2. * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
  3. * Copyright (C) 2008-2010, Google Inc.
  4. * Copyright (C) 2006-2010, Robin Rosenberg <robin.rosenberg@dewire.com>
  5. * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
  6. * and other copyright owners as documented in the project's IP log.
  7. *
  8. * This program and the accompanying materials are made available
  9. * under the terms of the Eclipse Distribution License v1.0 which
  10. * accompanies this distribution, is reproduced below, and is
  11. * available at http://www.eclipse.org/org/documents/edl-v10.php
  12. *
  13. * All rights reserved.
  14. *
  15. * Redistribution and use in source and binary forms, with or
  16. * without modification, are permitted provided that the following
  17. * conditions are met:
  18. *
  19. * - Redistributions of source code must retain the above copyright
  20. * notice, this list of conditions and the following disclaimer.
  21. *
  22. * - Redistributions in binary form must reproduce the above
  23. * copyright notice, this list of conditions and the following
  24. * disclaimer in the documentation and/or other materials provided
  25. * with the distribution.
  26. *
  27. * - Neither the name of the Eclipse Foundation, Inc. nor the
  28. * names of its contributors may be used to endorse or promote
  29. * products derived from this software without specific prior
  30. * written permission.
  31. *
  32. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  33. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  34. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  35. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  36. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  37. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  38. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  39. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  40. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  41. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  42. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  43. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  44. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  45. */
  46. package org.eclipse.jgit.lib;
  47. import java.io.File;
  48. import java.io.IOException;
  49. import java.util.ArrayList;
  50. import java.util.Collection;
  51. import java.util.Collections;
  52. import java.util.HashMap;
  53. import java.util.HashSet;
  54. import java.util.LinkedList;
  55. import java.util.List;
  56. import java.util.Map;
  57. import java.util.Set;
  58. import java.util.Vector;
  59. import java.util.concurrent.atomic.AtomicInteger;
  60. import org.eclipse.jgit.errors.ConfigInvalidException;
  61. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  62. import org.eclipse.jgit.errors.RevisionSyntaxException;
  63. import org.eclipse.jgit.util.FS;
  64. import org.eclipse.jgit.util.SystemReader;
  65. /**
  66. * Represents a Git repository. A repository holds all objects and refs used for
  67. * managing source code (could by any type of file, but source code is what
  68. * SCM's are typically used for).
  69. *
  70. * In Git terms all data is stored in GIT_DIR, typically a directory called
  71. * .git. A work tree is maintained unless the repository is a bare repository.
  72. * Typically the .git directory is located at the root of the work dir.
  73. *
  74. * <ul>
  75. * <li>GIT_DIR
  76. * <ul>
  77. * <li>objects/ - objects</li>
  78. * <li>refs/ - tags and heads</li>
  79. * <li>config - configuration</li>
  80. * <li>info/ - more configurations</li>
  81. * </ul>
  82. * </li>
  83. * </ul>
  84. * <p>
  85. * This class is thread-safe.
  86. * <p>
  87. * This implementation only handles a subtly undocumented subset of git features.
  88. *
  89. */
  90. public class Repository {
  91. private final AtomicInteger useCnt = new AtomicInteger(1);
  92. private final File gitDir;
  93. private final FileBasedConfig userConfig;
  94. private final RepositoryConfig config;
  95. private final RefDatabase refs;
  96. private final ObjectDirectory objectDatabase;
  97. private GitIndex index;
  98. private final List<RepositoryListener> listeners = new Vector<RepositoryListener>(); // thread safe
  99. static private final List<RepositoryListener> allListeners = new Vector<RepositoryListener>(); // thread safe
  100. private File workDir;
  101. private File indexFile;
  102. /**
  103. * Construct a representation of a Git repository.
  104. *
  105. * The work tree, object directory, alternate object directories and index
  106. * file locations are deduced from the given git directory and the default
  107. * rules.
  108. *
  109. * @param d
  110. * GIT_DIR (the location of the repository metadata).
  111. * @throws IOException
  112. * the repository appears to already exist but cannot be
  113. * accessed.
  114. */
  115. public Repository(final File d) throws IOException {
  116. this(d, null, null, null, null); // go figure it out
  117. }
  118. /**
  119. * Construct a representation of a Git repository.
  120. *
  121. * The work tree, object directory, alternate object directories and index
  122. * file locations are deduced from the given git directory and the default
  123. * rules.
  124. *
  125. * @param d
  126. * GIT_DIR (the location of the repository metadata). May be
  127. * null work workTree is set
  128. * @param workTree
  129. * GIT_WORK_TREE (the root of the checkout). May be null for
  130. * default value.
  131. * @throws IOException
  132. * the repository appears to already exist but cannot be
  133. * accessed.
  134. */
  135. public Repository(final File d, final File workTree) throws IOException {
  136. this(d, workTree, null, null, null); // go figure it out
  137. }
  138. /**
  139. * Construct a representation of a Git repository using the given parameters
  140. * possibly overriding default conventions.
  141. *
  142. * @param d
  143. * GIT_DIR (the location of the repository metadata). May be null
  144. * for default value in which case it depends on GIT_WORK_TREE.
  145. * @param workTree
  146. * GIT_WORK_TREE (the root of the checkout). May be null for
  147. * default value if GIT_DIR is
  148. * @param objectDir
  149. * GIT_OBJECT_DIRECTORY (where objects and are stored). May be
  150. * null for default value. Relative names ares resolved against
  151. * GIT_WORK_TREE
  152. * @param alternateObjectDir
  153. * GIT_ALTERNATE_OBJECT_DIRECTORIES (where more objects are read
  154. * from). May be null for default value. Relative names ares
  155. * resolved against GIT_WORK_TREE
  156. * @param indexFile
  157. * GIT_INDEX_FILE (the location of the index file). May be null
  158. * for default value. Relative names ares resolved against
  159. * GIT_WORK_TREE.
  160. * @throws IOException
  161. * the repository appears to already exist but cannot be
  162. * accessed.
  163. */
  164. public Repository(final File d, final File workTree, final File objectDir,
  165. final File[] alternateObjectDir, final File indexFile) throws IOException {
  166. if (workTree != null) {
  167. workDir = workTree;
  168. if (d == null)
  169. gitDir = new File(workTree, Constants.DOT_GIT);
  170. else
  171. gitDir = d;
  172. } else {
  173. if (d != null)
  174. gitDir = d;
  175. else
  176. throw new IllegalArgumentException("Either GIT_DIR or GIT_WORK_TREE must be passed to Repository constructor");
  177. }
  178. userConfig = SystemReader.getInstance().openUserConfig();
  179. config = new RepositoryConfig(userConfig, FS.resolve(gitDir, "config"));
  180. loadUserConfig();
  181. loadConfig();
  182. if (workDir == null) {
  183. String workTreeConfig = getConfig().getString("core", null, "worktree");
  184. if (workTreeConfig != null) {
  185. workDir = FS.resolve(d, workTreeConfig);
  186. } else {
  187. workDir = gitDir.getParentFile();
  188. }
  189. }
  190. refs = new RefDirectory(this);
  191. if (objectDir != null)
  192. objectDatabase = new ObjectDirectory(FS.resolve(objectDir, ""),
  193. alternateObjectDir);
  194. else
  195. objectDatabase = new ObjectDirectory(FS.resolve(gitDir, "objects"),
  196. alternateObjectDir);
  197. if (indexFile != null)
  198. this.indexFile = indexFile;
  199. else
  200. this.indexFile = new File(gitDir, "index");
  201. if (objectDatabase.exists()) {
  202. final String repositoryFormatVersion = getConfig().getString(
  203. "core", null, "repositoryFormatVersion");
  204. if (!"0".equals(repositoryFormatVersion)) {
  205. throw new IOException("Unknown repository format \""
  206. + repositoryFormatVersion + "\"; expected \"0\".");
  207. }
  208. }
  209. }
  210. private void loadUserConfig() throws IOException {
  211. try {
  212. userConfig.load();
  213. } catch (ConfigInvalidException e1) {
  214. IOException e2 = new IOException("User config file "
  215. + userConfig.getFile().getAbsolutePath() + " invalid: "
  216. + e1);
  217. e2.initCause(e1);
  218. throw e2;
  219. }
  220. }
  221. private void loadConfig() throws IOException {
  222. try {
  223. config.load();
  224. } catch (ConfigInvalidException e1) {
  225. IOException e2 = new IOException("Unknown repository format");
  226. e2.initCause(e1);
  227. throw e2;
  228. }
  229. }
  230. /**
  231. * Create a new Git repository initializing the necessary files and
  232. * directories. Repository with working tree is created using this method.
  233. *
  234. * @throws IOException
  235. * @see #create(boolean)
  236. */
  237. public synchronized void create() throws IOException {
  238. create(false);
  239. }
  240. /**
  241. * Create a new Git repository initializing the necessary files and
  242. * directories.
  243. *
  244. * @param bare
  245. * if true, a bare repository is created.
  246. *
  247. * @throws IOException
  248. * in case of IO problem
  249. */
  250. public void create(boolean bare) throws IOException {
  251. final RepositoryConfig cfg = getConfig();
  252. if (cfg.getFile().exists()) {
  253. throw new IllegalStateException("Repository already exists: "
  254. + gitDir);
  255. }
  256. gitDir.mkdirs();
  257. refs.create();
  258. objectDatabase.create();
  259. new File(gitDir, "branches").mkdir();
  260. RefUpdate head = updateRef(Constants.HEAD);
  261. head.disableRefLog();
  262. head.link(Constants.R_HEADS + Constants.MASTER);
  263. cfg.setInt("core", null, "repositoryformatversion", 0);
  264. cfg.setBoolean("core", null, "filemode", true);
  265. if (bare)
  266. cfg.setBoolean("core", null, "bare", true);
  267. cfg.setBoolean("core", null, "logallrefupdates", !bare);
  268. cfg.setBoolean("core", null, "autocrlf", false);
  269. cfg.save();
  270. }
  271. /**
  272. * @return GIT_DIR
  273. */
  274. public File getDirectory() {
  275. return gitDir;
  276. }
  277. /**
  278. * @return the directory containing the objects owned by this repository.
  279. */
  280. public File getObjectsDirectory() {
  281. return objectDatabase.getDirectory();
  282. }
  283. /**
  284. * @return the object database which stores this repository's data.
  285. */
  286. public ObjectDatabase getObjectDatabase() {
  287. return objectDatabase;
  288. }
  289. /** @return the reference database which stores the reference namespace. */
  290. public RefDatabase getRefDatabase() {
  291. return refs;
  292. }
  293. /**
  294. * @return the configuration of this repository
  295. */
  296. public RepositoryConfig getConfig() {
  297. if (userConfig.isOutdated()) {
  298. try {
  299. loadUserConfig();
  300. } catch (IOException e) {
  301. throw new RuntimeException(e);
  302. }
  303. }
  304. if (config.isOutdated()) {
  305. try {
  306. loadConfig();
  307. } catch (IOException e) {
  308. throw new RuntimeException(e);
  309. }
  310. }
  311. return config;
  312. }
  313. /**
  314. * Construct a filename where the loose object having a specified SHA-1
  315. * should be stored. If the object is stored in a shared repository the path
  316. * to the alternative repo will be returned. If the object is not yet store
  317. * a usable path in this repo will be returned. It is assumed that callers
  318. * will look for objects in a pack first.
  319. *
  320. * @param objectId
  321. * @return suggested file name
  322. */
  323. public File toFile(final AnyObjectId objectId) {
  324. return objectDatabase.fileFor(objectId);
  325. }
  326. /**
  327. * @param objectId
  328. * @return true if the specified object is stored in this repo or any of the
  329. * known shared repositories.
  330. */
  331. public boolean hasObject(final AnyObjectId objectId) {
  332. return objectDatabase.hasObject(objectId);
  333. }
  334. /**
  335. * @param id
  336. * SHA-1 of an object.
  337. *
  338. * @return a {@link ObjectLoader} for accessing the data of the named
  339. * object, or null if the object does not exist.
  340. * @throws IOException
  341. */
  342. public ObjectLoader openObject(final AnyObjectId id)
  343. throws IOException {
  344. final WindowCursor wc = new WindowCursor();
  345. try {
  346. return openObject(wc, id);
  347. } finally {
  348. wc.release();
  349. }
  350. }
  351. /**
  352. * @param curs
  353. * temporary working space associated with the calling thread.
  354. * @param id
  355. * SHA-1 of an object.
  356. *
  357. * @return a {@link ObjectLoader} for accessing the data of the named
  358. * object, or null if the object does not exist.
  359. * @throws IOException
  360. */
  361. public ObjectLoader openObject(final WindowCursor curs, final AnyObjectId id)
  362. throws IOException {
  363. return objectDatabase.openObject(curs, id);
  364. }
  365. /**
  366. * Open object in all packs containing specified object.
  367. *
  368. * @param objectId
  369. * id of object to search for
  370. * @param curs
  371. * temporary working space associated with the calling thread.
  372. * @return collection of loaders for this object, from all packs containing
  373. * this object
  374. * @throws IOException
  375. */
  376. public Collection<PackedObjectLoader> openObjectInAllPacks(
  377. final AnyObjectId objectId, final WindowCursor curs)
  378. throws IOException {
  379. Collection<PackedObjectLoader> result = new LinkedList<PackedObjectLoader>();
  380. openObjectInAllPacks(objectId, result, curs);
  381. return result;
  382. }
  383. /**
  384. * Open object in all packs containing specified object.
  385. *
  386. * @param objectId
  387. * id of object to search for
  388. * @param resultLoaders
  389. * result collection of loaders for this object, filled with
  390. * loaders from all packs containing specified object
  391. * @param curs
  392. * temporary working space associated with the calling thread.
  393. * @throws IOException
  394. */
  395. void openObjectInAllPacks(final AnyObjectId objectId,
  396. final Collection<PackedObjectLoader> resultLoaders,
  397. final WindowCursor curs) throws IOException {
  398. objectDatabase.openObjectInAllPacks(resultLoaders, curs, objectId);
  399. }
  400. /**
  401. * @param id
  402. * SHA'1 of a blob
  403. * @return an {@link ObjectLoader} for accessing the data of a named blob
  404. * @throws IOException
  405. */
  406. public ObjectLoader openBlob(final ObjectId id) throws IOException {
  407. return openObject(id);
  408. }
  409. /**
  410. * @param id
  411. * SHA'1 of a tree
  412. * @return an {@link ObjectLoader} for accessing the data of a named tree
  413. * @throws IOException
  414. */
  415. public ObjectLoader openTree(final ObjectId id) throws IOException {
  416. return openObject(id);
  417. }
  418. /**
  419. * Access a Commit object using a symbolic reference. This reference may
  420. * be a SHA-1 or ref in combination with a number of symbols translating
  421. * from one ref or SHA1-1 to another, such as HEAD^ etc.
  422. *
  423. * @param revstr a reference to a git commit object
  424. * @return a Commit named by the specified string
  425. * @throws IOException for I/O error or unexpected object type.
  426. *
  427. * @see #resolve(String)
  428. */
  429. public Commit mapCommit(final String revstr) throws IOException {
  430. final ObjectId id = resolve(revstr);
  431. return id != null ? mapCommit(id) : null;
  432. }
  433. /**
  434. * Access any type of Git object by id and
  435. *
  436. * @param id
  437. * SHA-1 of object to read
  438. * @param refName optional, only relevant for simple tags
  439. * @return The Git object if found or null
  440. * @throws IOException
  441. */
  442. public Object mapObject(final ObjectId id, final String refName) throws IOException {
  443. final ObjectLoader or = openObject(id);
  444. if (or == null)
  445. return null;
  446. final byte[] raw = or.getBytes();
  447. switch (or.getType()) {
  448. case Constants.OBJ_TREE:
  449. return makeTree(id, raw);
  450. case Constants.OBJ_COMMIT:
  451. return makeCommit(id, raw);
  452. case Constants.OBJ_TAG:
  453. return makeTag(id, refName, raw);
  454. case Constants.OBJ_BLOB:
  455. return raw;
  456. default:
  457. throw new IncorrectObjectTypeException(id,
  458. "COMMIT nor TREE nor BLOB nor TAG");
  459. }
  460. }
  461. /**
  462. * Access a Commit by SHA'1 id.
  463. * @param id
  464. * @return Commit or null
  465. * @throws IOException for I/O error or unexpected object type.
  466. */
  467. public Commit mapCommit(final ObjectId id) throws IOException {
  468. final ObjectLoader or = openObject(id);
  469. if (or == null)
  470. return null;
  471. final byte[] raw = or.getBytes();
  472. if (Constants.OBJ_COMMIT == or.getType())
  473. return new Commit(this, id, raw);
  474. throw new IncorrectObjectTypeException(id, Constants.TYPE_COMMIT);
  475. }
  476. private Commit makeCommit(final ObjectId id, final byte[] raw) {
  477. Commit ret = new Commit(this, id, raw);
  478. return ret;
  479. }
  480. /**
  481. * Access a Tree object using a symbolic reference. This reference may
  482. * be a SHA-1 or ref in combination with a number of symbols translating
  483. * from one ref or SHA1-1 to another, such as HEAD^{tree} etc.
  484. *
  485. * @param revstr a reference to a git commit object
  486. * @return a Tree named by the specified string
  487. * @throws IOException
  488. *
  489. * @see #resolve(String)
  490. */
  491. public Tree mapTree(final String revstr) throws IOException {
  492. final ObjectId id = resolve(revstr);
  493. return id != null ? mapTree(id) : null;
  494. }
  495. /**
  496. * Access a Tree by SHA'1 id.
  497. * @param id
  498. * @return Tree or null
  499. * @throws IOException for I/O error or unexpected object type.
  500. */
  501. public Tree mapTree(final ObjectId id) throws IOException {
  502. final ObjectLoader or = openObject(id);
  503. if (or == null)
  504. return null;
  505. final byte[] raw = or.getBytes();
  506. switch (or.getType()) {
  507. case Constants.OBJ_TREE:
  508. return new Tree(this, id, raw);
  509. case Constants.OBJ_COMMIT:
  510. return mapTree(ObjectId.fromString(raw, 5));
  511. default:
  512. throw new IncorrectObjectTypeException(id, Constants.TYPE_TREE);
  513. }
  514. }
  515. private Tree makeTree(final ObjectId id, final byte[] raw) throws IOException {
  516. Tree ret = new Tree(this, id, raw);
  517. return ret;
  518. }
  519. private Tag makeTag(final ObjectId id, final String refName, final byte[] raw) {
  520. Tag ret = new Tag(this, id, refName, raw);
  521. return ret;
  522. }
  523. /**
  524. * Access a tag by symbolic name.
  525. *
  526. * @param revstr
  527. * @return a Tag or null
  528. * @throws IOException on I/O error or unexpected type
  529. */
  530. public Tag mapTag(String revstr) throws IOException {
  531. final ObjectId id = resolve(revstr);
  532. return id != null ? mapTag(revstr, id) : null;
  533. }
  534. /**
  535. * Access a Tag by SHA'1 id
  536. * @param refName
  537. * @param id
  538. * @return Commit or null
  539. * @throws IOException for I/O error or unexpected object type.
  540. */
  541. public Tag mapTag(final String refName, final ObjectId id) throws IOException {
  542. final ObjectLoader or = openObject(id);
  543. if (or == null)
  544. return null;
  545. final byte[] raw = or.getBytes();
  546. if (Constants.OBJ_TAG == or.getType())
  547. return new Tag(this, id, refName, raw);
  548. return new Tag(this, id, refName, null);
  549. }
  550. /**
  551. * Create a command to update, create or delete a ref in this repository.
  552. *
  553. * @param ref
  554. * name of the ref the caller wants to modify.
  555. * @return an update command. The caller must finish populating this command
  556. * and then invoke one of the update methods to actually make a
  557. * change.
  558. * @throws IOException
  559. * a symbolic ref was passed in and could not be resolved back
  560. * to the base ref, as the symbolic ref could not be read.
  561. */
  562. public RefUpdate updateRef(final String ref) throws IOException {
  563. return updateRef(ref, false);
  564. }
  565. /**
  566. * Create a command to update, create or delete a ref in this repository.
  567. *
  568. * @param ref
  569. * name of the ref the caller wants to modify.
  570. * @param detach
  571. * true to create a detached head
  572. * @return an update command. The caller must finish populating this command
  573. * and then invoke one of the update methods to actually make a
  574. * change.
  575. * @throws IOException
  576. * a symbolic ref was passed in and could not be resolved back
  577. * to the base ref, as the symbolic ref could not be read.
  578. */
  579. public RefUpdate updateRef(final String ref, final boolean detach) throws IOException {
  580. return refs.newUpdate(ref, detach);
  581. }
  582. /**
  583. * Create a command to rename a ref in this repository
  584. *
  585. * @param fromRef
  586. * name of ref to rename from
  587. * @param toRef
  588. * name of ref to rename to
  589. * @return an update command that knows how to rename a branch to another.
  590. * @throws IOException
  591. * the rename could not be performed.
  592. *
  593. */
  594. public RefRename renameRef(final String fromRef, final String toRef) throws IOException {
  595. return refs.newRename(fromRef, toRef);
  596. }
  597. /**
  598. * Parse a git revision string and return an object id.
  599. *
  600. * Currently supported is combinations of these.
  601. * <ul>
  602. * <li>SHA-1 - a SHA-1</li>
  603. * <li>refs/... - a ref name</li>
  604. * <li>ref^n - nth parent reference</li>
  605. * <li>ref~n - distance via parent reference</li>
  606. * <li>ref@{n} - nth version of ref</li>
  607. * <li>ref^{tree} - tree references by ref</li>
  608. * <li>ref^{commit} - commit references by ref</li>
  609. * </ul>
  610. *
  611. * Not supported is
  612. * <ul>
  613. * <li>timestamps in reflogs, ref@{full or relative timestamp}</li>
  614. * <li>abbreviated SHA-1's</li>
  615. * </ul>
  616. *
  617. * @param revstr A git object references expression
  618. * @return an ObjectId or null if revstr can't be resolved to any ObjectId
  619. * @throws IOException on serious errors
  620. */
  621. public ObjectId resolve(final String revstr) throws IOException {
  622. char[] rev = revstr.toCharArray();
  623. Object ref = null;
  624. ObjectId refId = null;
  625. for (int i = 0; i < rev.length; ++i) {
  626. switch (rev[i]) {
  627. case '^':
  628. if (refId == null) {
  629. String refstr = new String(rev,0,i);
  630. refId = resolveSimple(refstr);
  631. if (refId == null)
  632. return null;
  633. }
  634. if (i + 1 < rev.length) {
  635. switch (rev[i + 1]) {
  636. case '0':
  637. case '1':
  638. case '2':
  639. case '3':
  640. case '4':
  641. case '5':
  642. case '6':
  643. case '7':
  644. case '8':
  645. case '9':
  646. int j;
  647. ref = mapObject(refId, null);
  648. while (ref instanceof Tag) {
  649. Tag tag = (Tag)ref;
  650. refId = tag.getObjId();
  651. ref = mapObject(refId, null);
  652. }
  653. if (!(ref instanceof Commit))
  654. throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
  655. for (j=i+1; j<rev.length; ++j) {
  656. if (!Character.isDigit(rev[j]))
  657. break;
  658. }
  659. String parentnum = new String(rev, i+1, j-i-1);
  660. int pnum;
  661. try {
  662. pnum = Integer.parseInt(parentnum);
  663. } catch (NumberFormatException e) {
  664. throw new RevisionSyntaxException(
  665. "Invalid commit parent number",
  666. revstr);
  667. }
  668. if (pnum != 0) {
  669. final ObjectId parents[] = ((Commit) ref)
  670. .getParentIds();
  671. if (pnum > parents.length)
  672. refId = null;
  673. else
  674. refId = parents[pnum - 1];
  675. }
  676. i = j - 1;
  677. break;
  678. case '{':
  679. int k;
  680. String item = null;
  681. for (k=i+2; k<rev.length; ++k) {
  682. if (rev[k] == '}') {
  683. item = new String(rev, i+2, k-i-2);
  684. break;
  685. }
  686. }
  687. i = k;
  688. if (item != null)
  689. if (item.equals("tree")) {
  690. ref = mapObject(refId, null);
  691. while (ref instanceof Tag) {
  692. Tag t = (Tag)ref;
  693. refId = t.getObjId();
  694. ref = mapObject(refId, null);
  695. }
  696. if (ref instanceof Treeish)
  697. refId = ((Treeish)ref).getTreeId();
  698. else
  699. throw new IncorrectObjectTypeException(refId, Constants.TYPE_TREE);
  700. }
  701. else if (item.equals("commit")) {
  702. ref = mapObject(refId, null);
  703. while (ref instanceof Tag) {
  704. Tag t = (Tag)ref;
  705. refId = t.getObjId();
  706. ref = mapObject(refId, null);
  707. }
  708. if (!(ref instanceof Commit))
  709. throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
  710. }
  711. else if (item.equals("blob")) {
  712. ref = mapObject(refId, null);
  713. while (ref instanceof Tag) {
  714. Tag t = (Tag)ref;
  715. refId = t.getObjId();
  716. ref = mapObject(refId, null);
  717. }
  718. if (!(ref instanceof byte[]))
  719. throw new IncorrectObjectTypeException(refId, Constants.TYPE_BLOB);
  720. }
  721. else if (item.equals("")) {
  722. ref = mapObject(refId, null);
  723. while (ref instanceof Tag) {
  724. Tag t = (Tag)ref;
  725. refId = t.getObjId();
  726. ref = mapObject(refId, null);
  727. }
  728. }
  729. else
  730. throw new RevisionSyntaxException(revstr);
  731. else
  732. throw new RevisionSyntaxException(revstr);
  733. break;
  734. default:
  735. ref = mapObject(refId, null);
  736. if (ref instanceof Commit) {
  737. final ObjectId parents[] = ((Commit) ref)
  738. .getParentIds();
  739. if (parents.length == 0)
  740. refId = null;
  741. else
  742. refId = parents[0];
  743. } else
  744. throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
  745. }
  746. } else {
  747. ref = mapObject(refId, null);
  748. while (ref instanceof Tag) {
  749. Tag tag = (Tag)ref;
  750. refId = tag.getObjId();
  751. ref = mapObject(refId, null);
  752. }
  753. if (ref instanceof Commit) {
  754. final ObjectId parents[] = ((Commit) ref)
  755. .getParentIds();
  756. if (parents.length == 0)
  757. refId = null;
  758. else
  759. refId = parents[0];
  760. } else
  761. throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
  762. }
  763. break;
  764. case '~':
  765. if (ref == null) {
  766. String refstr = new String(rev,0,i);
  767. refId = resolveSimple(refstr);
  768. if (refId == null)
  769. return null;
  770. ref = mapObject(refId, null);
  771. }
  772. while (ref instanceof Tag) {
  773. Tag tag = (Tag)ref;
  774. refId = tag.getObjId();
  775. ref = mapObject(refId, null);
  776. }
  777. if (!(ref instanceof Commit))
  778. throw new IncorrectObjectTypeException(refId, Constants.TYPE_COMMIT);
  779. int l;
  780. for (l = i + 1; l < rev.length; ++l) {
  781. if (!Character.isDigit(rev[l]))
  782. break;
  783. }
  784. String distnum = new String(rev, i+1, l-i-1);
  785. int dist;
  786. try {
  787. dist = Integer.parseInt(distnum);
  788. } catch (NumberFormatException e) {
  789. throw new RevisionSyntaxException(
  790. "Invalid ancestry length", revstr);
  791. }
  792. while (dist > 0) {
  793. final ObjectId[] parents = ((Commit) ref).getParentIds();
  794. if (parents.length == 0) {
  795. refId = null;
  796. break;
  797. }
  798. refId = parents[0];
  799. ref = mapCommit(refId);
  800. --dist;
  801. }
  802. i = l - 1;
  803. break;
  804. case '@':
  805. int m;
  806. String time = null;
  807. for (m=i+2; m<rev.length; ++m) {
  808. if (rev[m] == '}') {
  809. time = new String(rev, i+2, m-i-2);
  810. break;
  811. }
  812. }
  813. if (time != null)
  814. throw new RevisionSyntaxException("reflogs not yet supported by revision parser", revstr);
  815. i = m - 1;
  816. break;
  817. default:
  818. if (refId != null)
  819. throw new RevisionSyntaxException(revstr);
  820. }
  821. }
  822. if (refId == null)
  823. refId = resolveSimple(revstr);
  824. return refId;
  825. }
  826. private ObjectId resolveSimple(final String revstr) throws IOException {
  827. if (ObjectId.isId(revstr))
  828. return ObjectId.fromString(revstr);
  829. final Ref r = refs.getRef(revstr);
  830. return r != null ? r.getObjectId() : null;
  831. }
  832. /** Increment the use counter by one, requiring a matched {@link #close()}. */
  833. public void incrementOpen() {
  834. useCnt.incrementAndGet();
  835. }
  836. /**
  837. * Close all resources used by this repository
  838. */
  839. public void close() {
  840. if (useCnt.decrementAndGet() == 0) {
  841. objectDatabase.close();
  842. refs.close();
  843. }
  844. }
  845. /**
  846. * Add a single existing pack to the list of available pack files.
  847. *
  848. * @param pack
  849. * path of the pack file to open.
  850. * @param idx
  851. * path of the corresponding index file.
  852. * @throws IOException
  853. * index file could not be opened, read, or is not recognized as
  854. * a Git pack file index.
  855. */
  856. public void openPack(final File pack, final File idx) throws IOException {
  857. objectDatabase.openPack(pack, idx);
  858. }
  859. public String toString() {
  860. return "Repository[" + getDirectory() + "]";
  861. }
  862. /**
  863. * Get the name of the reference that {@code HEAD} points to.
  864. * <p>
  865. * This is essentially the same as doing:
  866. *
  867. * <pre>
  868. * return getRef(Constants.HEAD).getTarget().getName()
  869. * </pre>
  870. *
  871. * Except when HEAD is detached, in which case this method returns the
  872. * current ObjectId in hexadecimal string format.
  873. *
  874. * @return name of current branch (for example {@code refs/heads/master}) or
  875. * an ObjectId in hex format if the current branch is detached.
  876. * @throws IOException
  877. */
  878. public String getFullBranch() throws IOException {
  879. Ref head = getRef(Constants.HEAD);
  880. if (head == null)
  881. return null;
  882. if (head.isSymbolic())
  883. return head.getTarget().getName();
  884. if (head.getObjectId() != null)
  885. return head.getObjectId().name();
  886. return null;
  887. }
  888. /**
  889. * Get the short name of the current branch that {@code HEAD} points to.
  890. * <p>
  891. * This is essentially the same as {@link #getFullBranch()}, except the
  892. * leading prefix {@code refs/heads/} is removed from the reference before
  893. * it is returned to the caller.
  894. *
  895. * @return name of current branch (for example {@code master}), or an
  896. * ObjectId in hex format if the current branch is detached.
  897. * @throws IOException
  898. */
  899. public String getBranch() throws IOException {
  900. String name = getFullBranch();
  901. if (name != null)
  902. return shortenRefName(name);
  903. return name;
  904. }
  905. /**
  906. * Get a ref by name.
  907. *
  908. * @param name
  909. * the name of the ref to lookup. May be a short-hand form, e.g.
  910. * "master" which is is automatically expanded to
  911. * "refs/heads/master" if "refs/heads/master" already exists.
  912. * @return the Ref with the given name, or null if it does not exist
  913. * @throws IOException
  914. */
  915. public Ref getRef(final String name) throws IOException {
  916. return refs.getRef(name);
  917. }
  918. /**
  919. * @return mutable map of all known refs (heads, tags, remotes).
  920. */
  921. public Map<String, Ref> getAllRefs() {
  922. try {
  923. return refs.getRefs(RefDatabase.ALL);
  924. } catch (IOException e) {
  925. return new HashMap<String, Ref>();
  926. }
  927. }
  928. /**
  929. * @return mutable map of all tags; key is short tag name ("v1.0") and value
  930. * of the entry contains the ref with the full tag name
  931. * ("refs/tags/v1.0").
  932. */
  933. public Map<String, Ref> getTags() {
  934. try {
  935. return refs.getRefs(Constants.R_TAGS);
  936. } catch (IOException e) {
  937. return new HashMap<String, Ref>();
  938. }
  939. }
  940. /**
  941. * Peel a possibly unpeeled reference to an annotated tag.
  942. * <p>
  943. * If the ref cannot be peeled (as it does not refer to an annotated tag)
  944. * the peeled id stays null, but {@link Ref#isPeeled()} will be true.
  945. *
  946. * @param ref
  947. * The ref to peel
  948. * @return <code>ref</code> if <code>ref.isPeeled()</code> is true; else a
  949. * new Ref object representing the same data as Ref, but isPeeled()
  950. * will be true and getPeeledObjectId will contain the peeled object
  951. * (or null).
  952. */
  953. public Ref peel(final Ref ref) {
  954. try {
  955. return refs.peel(ref);
  956. } catch (IOException e) {
  957. // Historical accident; if the reference cannot be peeled due
  958. // to some sort of repository access problem we claim that the
  959. // same as if the reference was not an annotated tag.
  960. return ref;
  961. }
  962. }
  963. /**
  964. * @return a map with all objects referenced by a peeled ref.
  965. */
  966. public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
  967. Map<String, Ref> allRefs = getAllRefs();
  968. Map<AnyObjectId, Set<Ref>> ret = new HashMap<AnyObjectId, Set<Ref>>(allRefs.size());
  969. for (Ref ref : allRefs.values()) {
  970. ref = peel(ref);
  971. AnyObjectId target = ref.getPeeledObjectId();
  972. if (target == null)
  973. target = ref.getObjectId();
  974. // We assume most Sets here are singletons
  975. Set<Ref> oset = ret.put(target, Collections.singleton(ref));
  976. if (oset != null) {
  977. // that was not the case (rare)
  978. if (oset.size() == 1) {
  979. // Was a read-only singleton, we must copy to a new Set
  980. oset = new HashSet<Ref>(oset);
  981. }
  982. ret.put(target, oset);
  983. oset.add(ref);
  984. }
  985. }
  986. return ret;
  987. }
  988. /**
  989. * @return a representation of the index associated with this repo
  990. * @throws IOException
  991. */
  992. public GitIndex getIndex() throws IOException {
  993. if (index == null) {
  994. index = new GitIndex(this);
  995. index.read();
  996. } else {
  997. index.rereadIfNecessary();
  998. }
  999. return index;
  1000. }
  1001. /**
  1002. * @return the index file location
  1003. */
  1004. public File getIndexFile() {
  1005. return indexFile;
  1006. }
  1007. static byte[] gitInternalSlash(byte[] bytes) {
  1008. if (File.separatorChar == '/')
  1009. return bytes;
  1010. for (int i=0; i<bytes.length; ++i)
  1011. if (bytes[i] == File.separatorChar)
  1012. bytes[i] = '/';
  1013. return bytes;
  1014. }
  1015. /**
  1016. * @return an important state
  1017. */
  1018. public RepositoryState getRepositoryState() {
  1019. // Pre Git-1.6 logic
  1020. if (new File(getWorkDir(), ".dotest").exists())
  1021. return RepositoryState.REBASING;
  1022. if (new File(gitDir,".dotest-merge").exists())
  1023. return RepositoryState.REBASING_INTERACTIVE;
  1024. // From 1.6 onwards
  1025. if (new File(getDirectory(),"rebase-apply/rebasing").exists())
  1026. return RepositoryState.REBASING_REBASING;
  1027. if (new File(getDirectory(),"rebase-apply/applying").exists())
  1028. return RepositoryState.APPLY;
  1029. if (new File(getDirectory(),"rebase-apply").exists())
  1030. return RepositoryState.REBASING;
  1031. if (new File(getDirectory(),"rebase-merge/interactive").exists())
  1032. return RepositoryState.REBASING_INTERACTIVE;
  1033. if (new File(getDirectory(),"rebase-merge").exists())
  1034. return RepositoryState.REBASING_MERGE;
  1035. // Both versions
  1036. if (new File(gitDir,"MERGE_HEAD").exists())
  1037. return RepositoryState.MERGING;
  1038. if (new File(gitDir,"BISECT_LOG").exists())
  1039. return RepositoryState.BISECTING;
  1040. return RepositoryState.SAFE;
  1041. }
  1042. /**
  1043. * Check validity of a ref name. It must not contain character that has
  1044. * a special meaning in a Git object reference expression. Some other
  1045. * dangerous characters are also excluded.
  1046. *
  1047. * For portability reasons '\' is excluded
  1048. *
  1049. * @param refName
  1050. *
  1051. * @return true if refName is a valid ref name
  1052. */
  1053. public static boolean isValidRefName(final String refName) {
  1054. final int len = refName.length();
  1055. if (len == 0)
  1056. return false;
  1057. if (refName.endsWith(LockFile.SUFFIX))
  1058. return false;
  1059. int components = 1;
  1060. char p = '\0';
  1061. for (int i = 0; i < len; i++) {
  1062. final char c = refName.charAt(i);
  1063. if (c <= ' ')
  1064. return false;
  1065. switch (c) {
  1066. case '.':
  1067. switch (p) {
  1068. case '\0': case '/': case '.':
  1069. return false;
  1070. }
  1071. if (i == len -1)
  1072. return false;
  1073. break;
  1074. case '/':
  1075. if (i == 0 || i == len - 1)
  1076. return false;
  1077. components++;
  1078. break;
  1079. case '{':
  1080. if (p == '@')
  1081. return false;
  1082. break;
  1083. case '~': case '^': case ':':
  1084. case '?': case '[': case '*':
  1085. case '\\':
  1086. return false;
  1087. }
  1088. p = c;
  1089. }
  1090. return components > 1;
  1091. }
  1092. /**
  1093. * Strip work dir and return normalized repository path.
  1094. *
  1095. * @param workDir Work dir
  1096. * @param file File whose path shall be stripped of its workdir
  1097. * @return normalized repository relative path or the empty
  1098. * string if the file is not relative to the work directory.
  1099. */
  1100. public static String stripWorkDir(File workDir, File file) {
  1101. final String filePath = file.getPath();
  1102. final String workDirPath = workDir.getPath();
  1103. if (filePath.length() <= workDirPath.length() ||
  1104. filePath.charAt(workDirPath.length()) != File.separatorChar ||
  1105. !filePath.startsWith(workDirPath)) {
  1106. File absWd = workDir.isAbsolute() ? workDir : workDir.getAbsoluteFile();
  1107. File absFile = file.isAbsolute() ? file : file.getAbsoluteFile();
  1108. if (absWd == workDir && absFile == file)
  1109. return "";
  1110. return stripWorkDir(absWd, absFile);
  1111. }
  1112. String relName = filePath.substring(workDirPath.length() + 1);
  1113. if (File.separatorChar != '/')
  1114. relName = relName.replace(File.separatorChar, '/');
  1115. return relName;
  1116. }
  1117. /**
  1118. * @return the workdir file, i.e. where the files are checked out
  1119. */
  1120. public File getWorkDir() {
  1121. return workDir;
  1122. }
  1123. /**
  1124. * Override default workdir
  1125. *
  1126. * @param workTree
  1127. * the work tree directory
  1128. */
  1129. public void setWorkDir(File workTree) {
  1130. this.workDir = workTree;
  1131. }
  1132. /**
  1133. * Register a {@link RepositoryListener} which will be notified
  1134. * when ref changes are detected.
  1135. *
  1136. * @param l
  1137. */
  1138. public void addRepositoryChangedListener(final RepositoryListener l) {
  1139. listeners.add(l);
  1140. }
  1141. /**
  1142. * Remove a registered {@link RepositoryListener}
  1143. * @param l
  1144. */
  1145. public void removeRepositoryChangedListener(final RepositoryListener l) {
  1146. listeners.remove(l);
  1147. }
  1148. /**
  1149. * Register a global {@link RepositoryListener} which will be notified
  1150. * when a ref changes in any repository are detected.
  1151. *
  1152. * @param l
  1153. */
  1154. public static void addAnyRepositoryChangedListener(final RepositoryListener l) {
  1155. allListeners.add(l);
  1156. }
  1157. /**
  1158. * Remove a globally registered {@link RepositoryListener}
  1159. * @param l
  1160. */
  1161. public static void removeAnyRepositoryChangedListener(final RepositoryListener l) {
  1162. allListeners.remove(l);
  1163. }
  1164. void fireRefsChanged() {
  1165. final RefsChangedEvent event = new RefsChangedEvent(this);
  1166. List<RepositoryListener> all;
  1167. synchronized (listeners) {
  1168. all = new ArrayList<RepositoryListener>(listeners);
  1169. }
  1170. synchronized (allListeners) {
  1171. all.addAll(allListeners);
  1172. }
  1173. for (final RepositoryListener l : all) {
  1174. l.refsChanged(event);
  1175. }
  1176. }
  1177. void fireIndexChanged() {
  1178. final IndexChangedEvent event = new IndexChangedEvent(this);
  1179. List<RepositoryListener> all;
  1180. synchronized (listeners) {
  1181. all = new ArrayList<RepositoryListener>(listeners);
  1182. }
  1183. synchronized (allListeners) {
  1184. all.addAll(allListeners);
  1185. }
  1186. for (final RepositoryListener l : all) {
  1187. l.indexChanged(event);
  1188. }
  1189. }
  1190. /**
  1191. * Force a scan for changed refs.
  1192. *
  1193. * @throws IOException
  1194. */
  1195. public void scanForRepoChanges() throws IOException {
  1196. getAllRefs(); // This will look for changes to refs
  1197. getIndex(); // This will detect changes in the index
  1198. }
  1199. /**
  1200. * @param refName
  1201. *
  1202. * @return a more user friendly ref name
  1203. */
  1204. public String shortenRefName(String refName) {
  1205. if (refName.startsWith(Constants.R_HEADS))
  1206. return refName.substring(Constants.R_HEADS.length());
  1207. if (refName.startsWith(Constants.R_TAGS))
  1208. return refName.substring(Constants.R_TAGS.length());
  1209. if (refName.startsWith(Constants.R_REMOTES))
  1210. return refName.substring(Constants.R_REMOTES.length());
  1211. return refName;
  1212. }
  1213. /**
  1214. * @param refName
  1215. * @return a {@link ReflogReader} for the supplied refname, or null if the
  1216. * named ref does not exist.
  1217. * @throws IOException the ref could not be accessed.
  1218. */
  1219. public ReflogReader getReflogReader(String refName) throws IOException {
  1220. Ref ref = getRef(refName);
  1221. if (ref != null)
  1222. return new ReflogReader(this, ref.getName());
  1223. return null;
  1224. }
  1225. }