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-2012, Shawn O. Pearce <spearce@spearce.org>
6 * Copyright (C) 2012, Daniel Megert <daniel_megert@ch.ibm.com>
7 * Copyright (C) 2017, Wim Jongman <wim.jongman@remainsoftware.com>
8 * and other copyright owners as documented in the project's IP log.
9 *
10 * This program and the accompanying materials are made available
11 * under the terms of the Eclipse Distribution License v1.0 which
12 * accompanies this distribution, is reproduced below, and is
13 * available at http://www.eclipse.org/org/documents/edl-v10.php
14 *
15 * All rights reserved.
16 *
17 * Redistribution and use in source and binary forms, with or
18 * without modification, are permitted provided that the following
19 * conditions are met:
20 *
21 * - Redistributions of source code must retain the above copyright
22 * notice, this list of conditions and the following disclaimer.
23 *
24 * - Redistributions in binary form must reproduce the above
25 * copyright notice, this list of conditions and the following
26 * disclaimer in the documentation and/or other materials provided
27 * with the distribution.
28 *
29 * - Neither the name of the Eclipse Foundation, Inc. nor the
30 * names of its contributors may be used to endorse or promote
31 * products derived from this software without specific prior
32 * written permission.
33 *
34 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
35 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
36 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
38 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
39 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
41 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
42 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
43 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
44 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
46 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47 */
48
49 package org.eclipse.jgit.lib;
50
51 import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
52 import static java.nio.charset.StandardCharsets.UTF_8;
53
54 import java.io.BufferedOutputStream;
55 import java.io.File;
56 import java.io.FileNotFoundException;
57 import java.io.FileOutputStream;
58 import java.io.IOException;
59 import java.io.OutputStream;
60 import java.net.URISyntaxException;
61 import java.text.MessageFormat;
62 import java.util.Collection;
63 import java.util.Collections;
64 import java.util.HashMap;
65 import java.util.HashSet;
66 import java.util.LinkedList;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.Set;
70 import java.util.concurrent.atomic.AtomicInteger;
71 import java.util.concurrent.atomic.AtomicLong;
72 import java.util.regex.Pattern;
73
74 import org.eclipse.jgit.annotations.NonNull;
75 import org.eclipse.jgit.annotations.Nullable;
76 import org.eclipse.jgit.attributes.AttributesNodeProvider;
77 import org.eclipse.jgit.dircache.DirCache;
78 import org.eclipse.jgit.errors.AmbiguousObjectException;
79 import org.eclipse.jgit.errors.CorruptObjectException;
80 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
81 import org.eclipse.jgit.errors.MissingObjectException;
82 import org.eclipse.jgit.errors.NoWorkTreeException;
83 import org.eclipse.jgit.errors.RevisionSyntaxException;
84 import org.eclipse.jgit.events.IndexChangedEvent;
85 import org.eclipse.jgit.events.IndexChangedListener;
86 import org.eclipse.jgit.events.ListenerList;
87 import org.eclipse.jgit.events.RepositoryEvent;
88 import org.eclipse.jgit.internal.JGitText;
89 import org.eclipse.jgit.revwalk.RevBlob;
90 import org.eclipse.jgit.revwalk.RevCommit;
91 import org.eclipse.jgit.revwalk.RevObject;
92 import org.eclipse.jgit.revwalk.RevTree;
93 import org.eclipse.jgit.revwalk.RevWalk;
94 import org.eclipse.jgit.transport.RefSpec;
95 import org.eclipse.jgit.transport.RemoteConfig;
96 import org.eclipse.jgit.treewalk.TreeWalk;
97 import org.eclipse.jgit.util.FS;
98 import org.eclipse.jgit.util.FileUtils;
99 import org.eclipse.jgit.util.IO;
100 import org.eclipse.jgit.util.RawParseUtils;
101 import org.eclipse.jgit.util.SystemReader;
102 import org.slf4j.Logger;
103 import org.slf4j.LoggerFactory;
104
105 /**
106 * Represents a Git repository.
107 * <p>
108 * A repository holds all objects and refs used for managing source code (could
109 * be any type of file, but source code is what SCM's are typically used for).
110 * <p>
111 * The thread-safety of a {@link org.eclipse.jgit.lib.Repository} very much
112 * depends on the concrete implementation. Applications working with a generic
113 * {@code Repository} type must not assume the instance is thread-safe.
114 * <ul>
115 * <li>{@code FileRepository} is thread-safe.
116 * <li>{@code DfsRepository} thread-safety is determined by its subclass.
117 * </ul>
118 */
119 public abstract class Repository implements AutoCloseable {
120 private static final Logger LOG = LoggerFactory.getLogger(Repository.class);
121 private static final ListenerList globalListeners = new ListenerList();
122
123 /**
124 * Branch names containing slashes should not have a name component that is
125 * one of the reserved device names on Windows.
126 *
127 * @see #normalizeBranchName(String)
128 */
129 private static final Pattern FORBIDDEN_BRANCH_NAME_COMPONENTS = Pattern
130 .compile(
131 "(^|/)(aux|com[1-9]|con|lpt[1-9]|nul|prn)(\\.[^/]*)?", //$NON-NLS-1$
132 Pattern.CASE_INSENSITIVE);
133
134 /**
135 * Get the global listener list observing all events in this JVM.
136 *
137 * @return the global listener list observing all events in this JVM.
138 */
139 public static ListenerList getGlobalListenerList() {
140 return globalListeners;
141 }
142
143 /** Use counter */
144 final AtomicInteger useCnt = new AtomicInteger(1);
145
146 final AtomicLong closedAt = new AtomicLong();
147
148 /** Metadata directory holding the repository's critical files. */
149 private final File gitDir;
150
151 /** File abstraction used to resolve paths. */
152 private final FS fs;
153
154 private final ListenerList myListeners = new ListenerList();
155
156 /** If not bare, the top level directory of the working files. */
157 private final File workTree;
158
159 /** If not bare, the index file caching the working file states. */
160 private final File indexFile;
161
162 /**
163 * Initialize a new repository instance.
164 *
165 * @param options
166 * options to configure the repository.
167 */
168 protected Repository(BaseRepositoryBuilder options) {
169 gitDir = options.getGitDir();
170 fs = options.getFS();
171 workTree = options.getWorkTree();
172 indexFile = options.getIndexFile();
173 }
174
175 /**
176 * Get listeners observing only events on this repository.
177 *
178 * @return listeners observing only events on this repository.
179 */
180 @NonNull
181 public ListenerList getListenerList() {
182 return myListeners;
183 }
184
185 /**
186 * Fire an event to all registered listeners.
187 * <p>
188 * The source repository of the event is automatically set to this
189 * repository, before the event is delivered to any listeners.
190 *
191 * @param event
192 * the event to deliver.
193 */
194 public void fireEvent(RepositoryEvent<?> event) {
195 event.setRepository(this);
196 myListeners.dispatch(event);
197 globalListeners.dispatch(event);
198 }
199
200 /**
201 * Create a new Git repository.
202 * <p>
203 * Repository with working tree is created using this method. This method is
204 * the same as {@code create(false)}.
205 *
206 * @throws java.io.IOException
207 * @see #create(boolean)
208 */
209 public void create() throws IOException {
210 create(false);
211 }
212
213 /**
214 * Create a new Git repository initializing the necessary files and
215 * directories.
216 *
217 * @param bare
218 * if true, a bare repository (a repository without a working
219 * directory) is created.
220 * @throws java.io.IOException
221 * in case of IO problem
222 */
223 public abstract void create(boolean bare) throws IOException;
224
225 /**
226 * Get local metadata directory
227 *
228 * @return local metadata directory; {@code null} if repository isn't local.
229 */
230 /*
231 * TODO This method should be annotated as Nullable, because in some
232 * specific configurations metadata is not located in the local file system
233 * (for example in memory databases). In "usual" repositories this
234 * annotation would only cause compiler errors at places where the actual
235 * directory can never be null.
236 */
237 public File getDirectory() {
238 return gitDir;
239 }
240
241 /**
242 * Get the object database which stores this repository's data.
243 *
244 * @return the object database which stores this repository's data.
245 */
246 @NonNull
247 public abstract ObjectDatabase getObjectDatabase();
248
249 /**
250 * Create a new inserter to create objects in {@link #getObjectDatabase()}.
251 *
252 * @return a new inserter to create objects in {@link #getObjectDatabase()}.
253 */
254 @NonNull
255 public ObjectInserter newObjectInserter() {
256 return getObjectDatabase().newInserter();
257 }
258
259 /**
260 * Create a new reader to read objects from {@link #getObjectDatabase()}.
261 *
262 * @return a new reader to read objects from {@link #getObjectDatabase()}.
263 */
264 @NonNull
265 public ObjectReader newObjectReader() {
266 return getObjectDatabase().newReader();
267 }
268
269 /**
270 * Get the reference database which stores the reference namespace.
271 *
272 * @return the reference database which stores the reference namespace.
273 */
274 @NonNull
275 public abstract RefDatabase getRefDatabase();
276
277 /**
278 * Get the configuration of this repository.
279 *
280 * @return the configuration of this repository.
281 */
282 @NonNull
283 public abstract StoredConfig getConfig();
284
285 /**
286 * Create a new {@link org.eclipse.jgit.attributes.AttributesNodeProvider}.
287 *
288 * @return a new {@link org.eclipse.jgit.attributes.AttributesNodeProvider}.
289 * This {@link org.eclipse.jgit.attributes.AttributesNodeProvider}
290 * is lazy loaded only once. It means that it will not be updated
291 * after loading. Prefer creating new instance for each use.
292 * @since 4.2
293 */
294 @NonNull
295 public abstract AttributesNodeProvider createAttributesNodeProvider();
296
297 /**
298 * Get the used file system abstraction.
299 *
300 * @return the used file system abstraction, or or {@code null} if
301 * repository isn't local.
302 */
303 /*
304 * TODO This method should be annotated as Nullable, because in some
305 * specific configurations metadata is not located in the local file system
306 * (for example in memory databases). In "usual" repositories this
307 * annotation would only cause compiler errors at places where the actual
308 * directory can never be null.
309 */
310 public FS getFS() {
311 return fs;
312 }
313
314 /**
315 * Whether the specified object is stored in this repo or any of the known
316 * shared repositories.
317 *
318 * @param objectId
319 * a {@link org.eclipse.jgit.lib.AnyObjectId} object.
320 * @return true if the specified object is stored in this repo or any of the
321 * known shared repositories.
322 */
323 public boolean hasObject(AnyObjectId objectId) {
324 try {
325 return getObjectDatabase().has(objectId);
326 } catch (IOException e) {
327 // Legacy API, assume error means "no"
328 return false;
329 }
330 }
331
332 /**
333 * Open an object from this repository.
334 * <p>
335 * This is a one-shot call interface which may be faster than allocating a
336 * {@link #newObjectReader()} to perform the lookup.
337 *
338 * @param objectId
339 * identity of the object to open.
340 * @return a {@link org.eclipse.jgit.lib.ObjectLoader} for accessing the
341 * object.
342 * @throws org.eclipse.jgit.errors.MissingObjectException
343 * the object does not exist.
344 * @throws java.io.IOException
345 * the object store cannot be accessed.
346 */
347 @NonNull
348 public ObjectLoader open(AnyObjectId objectId)
349 throws MissingObjectException, IOException {
350 return getObjectDatabase().open(objectId);
351 }
352
353 /**
354 * Open an object from this repository.
355 * <p>
356 * This is a one-shot call interface which may be faster than allocating a
357 * {@link #newObjectReader()} to perform the lookup.
358 *
359 * @param objectId
360 * identity of the object to open.
361 * @param typeHint
362 * hint about the type of object being requested, e.g.
363 * {@link org.eclipse.jgit.lib.Constants#OBJ_BLOB};
364 * {@link org.eclipse.jgit.lib.ObjectReader#OBJ_ANY} if the
365 * object type is not known, or does not matter to the caller.
366 * @return a {@link org.eclipse.jgit.lib.ObjectLoader} for accessing the
367 * object.
368 * @throws org.eclipse.jgit.errors.MissingObjectException
369 * the object does not exist.
370 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
371 * typeHint was not OBJ_ANY, and the object's actual type does
372 * not match typeHint.
373 * @throws java.io.IOException
374 * the object store cannot be accessed.
375 */
376 @NonNull
377 public ObjectLoader open(AnyObjectId objectId, int typeHint)
378 throws MissingObjectException, IncorrectObjectTypeException,
379 IOException {
380 return getObjectDatabase().open(objectId, typeHint);
381 }
382
383 /**
384 * Create a command to update, create or delete a ref in this repository.
385 *
386 * @param ref
387 * name of the ref the caller wants to modify.
388 * @return an update command. The caller must finish populating this command
389 * and then invoke one of the update methods to actually make a
390 * change.
391 * @throws java.io.IOException
392 * a symbolic ref was passed in and could not be resolved back
393 * to the base ref, as the symbolic ref could not be read.
394 */
395 @NonNull
396 public RefUpdate updateRef(String ref) throws IOException {
397 return updateRef(ref, false);
398 }
399
400 /**
401 * Create a command to update, create or delete a ref in this repository.
402 *
403 * @param ref
404 * name of the ref the caller wants to modify.
405 * @param detach
406 * true to create a detached head
407 * @return an update command. The caller must finish populating this command
408 * and then invoke one of the update methods to actually make a
409 * change.
410 * @throws java.io.IOException
411 * a symbolic ref was passed in and could not be resolved back
412 * to the base ref, as the symbolic ref could not be read.
413 */
414 @NonNull
415 public RefUpdate updateRef(String ref, boolean detach) throws IOException {
416 return getRefDatabase().newUpdate(ref, detach);
417 }
418
419 /**
420 * Create a command to rename a ref in this repository
421 *
422 * @param fromRef
423 * name of ref to rename from
424 * @param toRef
425 * name of ref to rename to
426 * @return an update command that knows how to rename a branch to another.
427 * @throws java.io.IOException
428 * the rename could not be performed.
429 */
430 @NonNull
431 public RefRename renameRef(String fromRef, String toRef) throws IOException {
432 return getRefDatabase().newRename(fromRef, toRef);
433 }
434
435 /**
436 * Parse a git revision string and return an object id.
437 *
438 * Combinations of these operators are supported:
439 * <ul>
440 * <li><b>HEAD</b>, <b>MERGE_HEAD</b>, <b>FETCH_HEAD</b></li>
441 * <li><b>SHA-1</b>: a complete or abbreviated SHA-1</li>
442 * <li><b>refs/...</b>: a complete reference name</li>
443 * <li><b>short-name</b>: a short reference name under {@code refs/heads},
444 * {@code refs/tags}, or {@code refs/remotes} namespace</li>
445 * <li><b>tag-NN-gABBREV</b>: output from describe, parsed by treating
446 * {@code ABBREV} as an abbreviated SHA-1.</li>
447 * <li><i>id</i><b>^</b>: first parent of commit <i>id</i>, this is the same
448 * as {@code id^1}</li>
449 * <li><i>id</i><b>^0</b>: ensure <i>id</i> is a commit</li>
450 * <li><i>id</i><b>^n</b>: n-th parent of commit <i>id</i></li>
451 * <li><i>id</i><b>~n</b>: n-th historical ancestor of <i>id</i>, by first
452 * parent. {@code id~3} is equivalent to {@code id^1^1^1} or {@code id^^^}.</li>
453 * <li><i>id</i><b>:path</b>: Lookup path under tree named by <i>id</i></li>
454 * <li><i>id</i><b>^{commit}</b>: ensure <i>id</i> is a commit</li>
455 * <li><i>id</i><b>^{tree}</b>: ensure <i>id</i> is a tree</li>
456 * <li><i>id</i><b>^{tag}</b>: ensure <i>id</i> is a tag</li>
457 * <li><i>id</i><b>^{blob}</b>: ensure <i>id</i> is a blob</li>
458 * </ul>
459 *
460 * <p>
461 * The following operators are specified by Git conventions, but are not
462 * supported by this method:
463 * <ul>
464 * <li><b>ref@{n}</b>: n-th version of ref as given by its reflog</li>
465 * <li><b>ref@{time}</b>: value of ref at the designated time</li>
466 * </ul>
467 *
468 * @param revstr
469 * A git object references expression
470 * @return an ObjectId or {@code null} if revstr can't be resolved to any
471 * ObjectId
472 * @throws org.eclipse.jgit.errors.AmbiguousObjectException
473 * {@code revstr} contains an abbreviated ObjectId and this
474 * repository contains more than one object which match to the
475 * input abbreviation.
476 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
477 * the id parsed does not meet the type required to finish
478 * applying the operators in the expression.
479 * @throws org.eclipse.jgit.errors.RevisionSyntaxException
480 * the expression is not supported by this implementation, or
481 * does not meet the standard syntax.
482 * @throws java.io.IOException
483 * on serious errors
484 */
485 @Nullable
486 public ObjectId resolve(String revstr)
487 throws AmbiguousObjectException, IncorrectObjectTypeException,
488 RevisionSyntaxException, IOException {
489 try (RevWalk rw = new RevWalk(this)) {
490 Object resolved = resolve(rw, revstr);
491 if (resolved instanceof String) {
492 final Ref ref = findRef((String) resolved);
493 return ref != null ? ref.getLeaf().getObjectId() : null;
494 } else {
495 return (ObjectId) resolved;
496 }
497 }
498 }
499
500 /**
501 * Simplify an expression, but unlike {@link #resolve(String)} it will not
502 * resolve a branch passed or resulting from the expression, such as @{-}.
503 * Thus this method can be used to process an expression to a method that
504 * expects a branch or revision id.
505 *
506 * @param revstr a {@link java.lang.String} object.
507 * @return object id or ref name from resolved expression or {@code null} if
508 * given expression cannot be resolved
509 * @throws org.eclipse.jgit.errors.AmbiguousObjectException
510 * @throws java.io.IOException
511 */
512 @Nullable
513 public String simplify(String revstr)
514 throws AmbiguousObjectException, IOException {
515 try (RevWalk rw = new RevWalk(this)) {
516 Object resolved = resolve(rw, revstr);
517 if (resolved != null)
518 if (resolved instanceof String)
519 return (String) resolved;
520 else
521 return ((AnyObjectId) resolved).getName();
522 return null;
523 }
524 }
525
526 @Nullable
527 private Object resolve(RevWalk rw, String revstr)
528 throws IOException {
529 char[] revChars = revstr.toCharArray();
530 RevObject rev = null;
531 String name = null;
532 int done = 0;
533 for (int i = 0; i < revChars.length; ++i) {
534 switch (revChars[i]) {
535 case '^':
536 if (rev == null) {
537 if (name == null)
538 if (done == 0)
539 name = new String(revChars, done, i);
540 else {
541 done = i + 1;
542 break;
543 }
544 rev = parseSimple(rw, name);
545 name = null;
546 if (rev == null)
547 return null;
548 }
549 if (i + 1 < revChars.length) {
550 switch (revChars[i + 1]) {
551 case '0':
552 case '1':
553 case '2':
554 case '3':
555 case '4':
556 case '5':
557 case '6':
558 case '7':
559 case '8':
560 case '9':
561 int j;
562 rev = rw.parseCommit(rev);
563 for (j = i + 1; j < revChars.length; ++j) {
564 if (!Character.isDigit(revChars[j]))
565 break;
566 }
567 String parentnum = new String(revChars, i + 1, j - i
568 - 1);
569 int pnum;
570 try {
571 pnum = Integer.parseInt(parentnum);
572 } catch (NumberFormatException e) {
573 throw new RevisionSyntaxException(
574 JGitText.get().invalidCommitParentNumber,
575 revstr);
576 }
577 if (pnum != 0) {
578 RevCommit commit = (RevCommit) rev;
579 if (pnum > commit.getParentCount())
580 rev = null;
581 else
582 rev = commit.getParent(pnum - 1);
583 }
584 i = j - 1;
585 done = j;
586 break;
587 case '{':
588 int k;
589 String item = null;
590 for (k = i + 2; k < revChars.length; ++k) {
591 if (revChars[k] == '}') {
592 item = new String(revChars, i + 2, k - i - 2);
593 break;
594 }
595 }
596 i = k;
597 if (item != null)
598 if (item.equals("tree")) { //$NON-NLS-1$
599 rev = rw.parseTree(rev);
600 } else if (item.equals("commit")) { //$NON-NLS-1$
601 rev = rw.parseCommit(rev);
602 } else if (item.equals("blob")) { //$NON-NLS-1$
603 rev = rw.peel(rev);
604 if (!(rev instanceof RevBlob))
605 throw new IncorrectObjectTypeException(rev,
606 Constants.TYPE_BLOB);
607 } else if (item.equals("")) { //$NON-NLS-1$
608 rev = rw.peel(rev);
609 } else
610 throw new RevisionSyntaxException(revstr);
611 else
612 throw new RevisionSyntaxException(revstr);
613 done = k;
614 break;
615 default:
616 rev = rw.peel(rev);
617 if (rev instanceof RevCommit) {
618 RevCommit commit = ((RevCommit) rev);
619 if (commit.getParentCount() == 0)
620 rev = null;
621 else
622 rev = commit.getParent(0);
623 } else
624 throw new IncorrectObjectTypeException(rev,
625 Constants.TYPE_COMMIT);
626 }
627 } else {
628 rev = rw.peel(rev);
629 if (rev instanceof RevCommit) {
630 RevCommit commit = ((RevCommit) rev);
631 if (commit.getParentCount() == 0)
632 rev = null;
633 else
634 rev = commit.getParent(0);
635 } else
636 throw new IncorrectObjectTypeException(rev,
637 Constants.TYPE_COMMIT);
638 }
639 done = i + 1;
640 break;
641 case '~':
642 if (rev == null) {
643 if (name == null)
644 if (done == 0)
645 name = new String(revChars, done, i);
646 else {
647 done = i + 1;
648 break;
649 }
650 rev = parseSimple(rw, name);
651 name = null;
652 if (rev == null)
653 return null;
654 }
655 rev = rw.peel(rev);
656 if (!(rev instanceof RevCommit))
657 throw new IncorrectObjectTypeException(rev,
658 Constants.TYPE_COMMIT);
659 int l;
660 for (l = i + 1; l < revChars.length; ++l) {
661 if (!Character.isDigit(revChars[l]))
662 break;
663 }
664 int dist;
665 if (l - i > 1) {
666 String distnum = new String(revChars, i + 1, l - i - 1);
667 try {
668 dist = Integer.parseInt(distnum);
669 } catch (NumberFormatException e) {
670 throw new RevisionSyntaxException(
671 JGitText.get().invalidAncestryLength, revstr);
672 }
673 } else
674 dist = 1;
675 while (dist > 0) {
676 RevCommit commit = (RevCommit) rev;
677 if (commit.getParentCount() == 0) {
678 rev = null;
679 break;
680 }
681 commit = commit.getParent(0);
682 rw.parseHeaders(commit);
683 rev = commit;
684 --dist;
685 }
686 i = l - 1;
687 done = l;
688 break;
689 case '@':
690 if (rev != null)
691 throw new RevisionSyntaxException(revstr);
692 if (i + 1 == revChars.length)
693 continue;
694 if (i + 1 < revChars.length && revChars[i + 1] != '{')
695 continue;
696 int m;
697 String time = null;
698 for (m = i + 2; m < revChars.length; ++m) {
699 if (revChars[m] == '}') {
700 time = new String(revChars, i + 2, m - i - 2);
701 break;
702 }
703 }
704 if (time != null) {
705 if (time.equals("upstream")) { //$NON-NLS-1$
706 if (name == null)
707 name = new String(revChars, done, i);
708 if (name.equals("")) //$NON-NLS-1$
709 // Currently checked out branch, HEAD if
710 // detached
711 name = Constants.HEAD;
712 if (!Repository.isValidRefName("x/" + name)) //$NON-NLS-1$
713 throw new RevisionSyntaxException(MessageFormat
714 .format(JGitText.get().invalidRefName,
715 name),
716 revstr);
717 Ref ref = findRef(name);
718 name = null;
719 if (ref == null)
720 return null;
721 if (ref.isSymbolic())
722 ref = ref.getLeaf();
723 name = ref.getName();
724
725 RemoteConfig remoteConfig;
726 try {
727 remoteConfig = new RemoteConfig(getConfig(),
728 "origin"); //$NON-NLS-1$
729 } catch (URISyntaxException e) {
730 throw new RevisionSyntaxException(revstr);
731 }
732 String remoteBranchName = getConfig()
733 .getString(
734 ConfigConstants.CONFIG_BRANCH_SECTION,
735 Repository.shortenRefName(ref.getName()),
736 ConfigConstants.CONFIG_KEY_MERGE);
737 List<RefSpec> fetchRefSpecs = remoteConfig
738 .getFetchRefSpecs();
739 for (RefSpec refSpec : fetchRefSpecs) {
740 if (refSpec.matchSource(remoteBranchName)) {
741 RefSpec expandFromSource = refSpec
742 .expandFromSource(remoteBranchName);
743 name = expandFromSource.getDestination();
744 break;
745 }
746 }
747 if (name == null)
748 throw new RevisionSyntaxException(revstr);
749 } else if (time.matches("^-\\d+$")) { //$NON-NLS-1$
750 if (name != null)
751 throw new RevisionSyntaxException(revstr);
752 else {
753 String previousCheckout = resolveReflogCheckout(-Integer
754 .parseInt(time));
755 if (ObjectId.isId(previousCheckout))
756 rev = parseSimple(rw, previousCheckout);
757 else
758 name = previousCheckout;
759 }
760 } else {
761 if (name == null)
762 name = new String(revChars, done, i);
763 if (name.equals("")) //$NON-NLS-1$
764 name = Constants.HEAD;
765 if (!Repository.isValidRefName("x/" + name)) //$NON-NLS-1$
766 throw new RevisionSyntaxException(MessageFormat
767 .format(JGitText.get().invalidRefName,
768 name),
769 revstr);
770 Ref ref = findRef(name);
771 name = null;
772 if (ref == null)
773 return null;
774 // @{n} means current branch, not HEAD@{1} unless
775 // detached
776 if (ref.isSymbolic())
777 ref = ref.getLeaf();
778 rev = resolveReflog(rw, ref, time);
779 }
780 i = m;
781 } else
782 throw new RevisionSyntaxException(revstr);
783 break;
784 case ':': {
785 RevTree tree;
786 if (rev == null) {
787 if (name == null)
788 name = new String(revChars, done, i);
789 if (name.equals("")) //$NON-NLS-1$
790 name = Constants.HEAD;
791 rev = parseSimple(rw, name);
792 name = null;
793 }
794 if (rev == null)
795 return null;
796 tree = rw.parseTree(rev);
797 if (i == revChars.length - 1)
798 return tree.copy();
799
800 TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(),
801 new String(revChars, i + 1, revChars.length - i - 1),
802 tree);
803 return tw != null ? tw.getObjectId(0) : null;
804 }
805 default:
806 if (rev != null)
807 throw new RevisionSyntaxException(revstr);
808 }
809 }
810 if (rev != null)
811 return rev.copy();
812 if (name != null)
813 return name;
814 if (done == revstr.length())
815 return null;
816 name = revstr.substring(done);
817 if (!Repository.isValidRefName("x/" + name)) //$NON-NLS-1$
818 throw new RevisionSyntaxException(
819 MessageFormat.format(JGitText.get().invalidRefName, name),
820 revstr);
821 if (findRef(name) != null)
822 return name;
823 return resolveSimple(name);
824 }
825
826 private static boolean isHex(char c) {
827 return ('0' <= c && c <= '9') //
828 || ('a' <= c && c <= 'f') //
829 || ('A' <= c && c <= 'F');
830 }
831
832 private static boolean isAllHex(String str, int ptr) {
833 while (ptr < str.length()) {
834 if (!isHex(str.charAt(ptr++)))
835 return false;
836 }
837 return true;
838 }
839
840 @Nullable
841 private RevObject parseSimple(RevWalk rw, String revstr) throws IOException {
842 ObjectId id = resolveSimple(revstr);
843 return id != null ? rw.parseAny(id) : null;
844 }
845
846 @Nullable
847 private ObjectId resolveSimple(String revstr) throws IOException {
848 if (ObjectId.isId(revstr))
849 return ObjectId.fromString(revstr);
850
851 if (Repository.isValidRefName("x/" + revstr)) { //$NON-NLS-1$
852 Ref r = getRefDatabase().getRef(revstr);
853 if (r != null)
854 return r.getObjectId();
855 }
856
857 if (AbbreviatedObjectId.isId(revstr))
858 return resolveAbbreviation(revstr);
859
860 int dashg = revstr.indexOf("-g"); //$NON-NLS-1$
861 if ((dashg + 5) < revstr.length() && 0 <= dashg
862 && isHex(revstr.charAt(dashg + 2))
863 && isHex(revstr.charAt(dashg + 3))
864 && isAllHex(revstr, dashg + 4)) {
865 // Possibly output from git describe?
866 String s = revstr.substring(dashg + 2);
867 if (AbbreviatedObjectId.isId(s))
868 return resolveAbbreviation(s);
869 }
870
871 return null;
872 }
873
874 @Nullable
875 private String resolveReflogCheckout(int checkoutNo)
876 throws IOException {
877 ReflogReader reader = getReflogReader(Constants.HEAD);
878 if (reader == null) {
879 return null;
880 }
881 List<ReflogEntry> reflogEntries = reader.getReverseEntries();
882 for (ReflogEntry entry : reflogEntries) {
883 CheckoutEntry checkout = entry.parseCheckout();
884 if (checkout != null)
885 if (checkoutNo-- == 1)
886 return checkout.getFromBranch();
887 }
888 return null;
889 }
890
891 private RevCommit resolveReflog(RevWalk rw, Ref ref, String time)
892 throws IOException {
893 int number;
894 try {
895 number = Integer.parseInt(time);
896 } catch (NumberFormatException nfe) {
897 throw new RevisionSyntaxException(MessageFormat.format(
898 JGitText.get().invalidReflogRevision, time));
899 }
900 assert number >= 0;
901 ReflogReader reader = getReflogReader(ref.getName());
902 if (reader == null) {
903 throw new RevisionSyntaxException(
904 MessageFormat.format(JGitText.get().reflogEntryNotFound,
905 Integer.valueOf(number), ref.getName()));
906 }
907 ReflogEntry entry = reader.getReverseEntry(number);
908 if (entry == null)
909 throw new RevisionSyntaxException(MessageFormat.format(
910 JGitText.get().reflogEntryNotFound,
911 Integer.valueOf(number), ref.getName()));
912
913 return rw.parseCommit(entry.getNewId());
914 }
915
916 @Nullable
917 private ObjectId resolveAbbreviation(String revstr) throws IOException,
918 AmbiguousObjectException {
919 AbbreviatedObjectId id = AbbreviatedObjectId.fromString(revstr);
920 try (ObjectReader reader = newObjectReader()) {
921 Collection<ObjectId> matches = reader.resolve(id);
922 if (matches.size() == 0)
923 return null;
924 else if (matches.size() == 1)
925 return matches.iterator().next();
926 else
927 throw new AmbiguousObjectException(id, matches);
928 }
929 }
930
931 /**
932 * Increment the use counter by one, requiring a matched {@link #close()}.
933 */
934 public void incrementOpen() {
935 useCnt.incrementAndGet();
936 }
937
938 /**
939 * {@inheritDoc}
940 * <p>
941 * Decrement the use count, and maybe close resources.
942 */
943 @Override
944 public void close() {
945 int newCount = useCnt.decrementAndGet();
946 if (newCount == 0) {
947 if (RepositoryCache.isCached(this)) {
948 closedAt.set(System.currentTimeMillis());
949 } else {
950 doClose();
951 }
952 } else if (newCount == -1) {
953 // should not happen, only log when useCnt became negative to
954 // minimize number of log entries
955 String message = MessageFormat.format(JGitText.get().corruptUseCnt,
956 toString());
957 if (LOG.isDebugEnabled()) {
958 LOG.debug(message, new IllegalStateException());
959 } else {
960 LOG.warn(message);
961 }
962 if (RepositoryCache.isCached(this)) {
963 closedAt.set(System.currentTimeMillis());
964 }
965 }
966 }
967
968 /**
969 * Invoked when the use count drops to zero during {@link #close()}.
970 * <p>
971 * The default implementation closes the object and ref databases.
972 */
973 protected void doClose() {
974 getObjectDatabase().close();
975 getRefDatabase().close();
976 }
977
978 /** {@inheritDoc} */
979 @Override
980 @NonNull
981 public String toString() {
982 String desc;
983 File directory = getDirectory();
984 if (directory != null)
985 desc = directory.getPath();
986 else
987 desc = getClass().getSimpleName() + "-" //$NON-NLS-1$
988 + System.identityHashCode(this);
989 return "Repository[" + desc + "]"; //$NON-NLS-1$ //$NON-NLS-2$
990 }
991
992 /**
993 * Get the name of the reference that {@code HEAD} points to.
994 * <p>
995 * This is essentially the same as doing:
996 *
997 * <pre>
998 * return exactRef(Constants.HEAD).getTarget().getName()
999 * </pre>
1000 *
1001 * Except when HEAD is detached, in which case this method returns the
1002 * current ObjectId in hexadecimal string format.
1003 *
1004 * @return name of current branch (for example {@code refs/heads/master}),
1005 * an ObjectId in hex format if the current branch is detached, or
1006 * {@code null} if the repository is corrupt and has no HEAD
1007 * reference.
1008 * @throws java.io.IOException
1009 */
1010 @Nullable
1011 public String getFullBranch() throws IOException {
1012 Ref head = exactRef(Constants.HEAD);
1013 if (head == null) {
1014 return null;
1015 }
1016 if (head.isSymbolic()) {
1017 return head.getTarget().getName();
1018 }
1019 ObjectId objectId = head.getObjectId();
1020 if (objectId != null) {
1021 return objectId.name();
1022 }
1023 return null;
1024 }
1025
1026 /**
1027 * Get the short name of the current branch that {@code HEAD} points to.
1028 * <p>
1029 * This is essentially the same as {@link #getFullBranch()}, except the
1030 * leading prefix {@code refs/heads/} is removed from the reference before
1031 * it is returned to the caller.
1032 *
1033 * @return name of current branch (for example {@code master}), an ObjectId
1034 * in hex format if the current branch is detached, or {@code null}
1035 * if the repository is corrupt and has no HEAD reference.
1036 * @throws java.io.IOException
1037 */
1038 @Nullable
1039 public String getBranch() throws IOException {
1040 String name = getFullBranch();
1041 if (name != null)
1042 return shortenRefName(name);
1043 return null;
1044 }
1045
1046 /**
1047 * Objects known to exist but not expressed by {@link #getAllRefs()}.
1048 * <p>
1049 * When a repository borrows objects from another repository, it can
1050 * advertise that it safely has that other repository's references, without
1051 * exposing any other details about the other repository. This may help
1052 * a client trying to push changes avoid pushing more than it needs to.
1053 *
1054 * @return unmodifiable collection of other known objects.
1055 */
1056 @NonNull
1057 public Set<ObjectId> getAdditionalHaves() {
1058 return Collections.emptySet();
1059 }
1060
1061 /**
1062 * Get a ref by name.
1063 *
1064 * @param name
1065 * the name of the ref to lookup. Must not be a short-hand
1066 * form; e.g., "master" is not automatically expanded to
1067 * "refs/heads/master".
1068 * @return the Ref with the given name, or {@code null} if it does not exist
1069 * @throws java.io.IOException
1070 * @since 4.2
1071 */
1072 @Nullable
1073 public final Ref exactRef(String name) throws IOException {
1074 return getRefDatabase().exactRef(name);
1075 }
1076
1077 /**
1078 * Search for a ref by (possibly abbreviated) name.
1079 *
1080 * @param name
1081 * the name of the ref to lookup. May be a short-hand form, e.g.
1082 * "master" which is is automatically expanded to
1083 * "refs/heads/master" if "refs/heads/master" already exists.
1084 * @return the Ref with the given name, or {@code null} if it does not exist
1085 * @throws java.io.IOException
1086 * @since 4.2
1087 */
1088 @Nullable
1089 public final Ref findRef(String name) throws IOException {
1090 return getRefDatabase().getRef(name);
1091 }
1092
1093 /**
1094 * Get mutable map of all known refs, including symrefs like HEAD that may
1095 * not point to any object yet.
1096 *
1097 * @return mutable map of all known refs (heads, tags, remotes).
1098 * @deprecated use {@code getRefDatabase().getRefs()} instead.
1099 */
1100 @Deprecated
1101 @NonNull
1102 public Map<String, Ref> getAllRefs() {
1103 try {
1104 return getRefDatabase().getRefs(RefDatabase.ALL);
1105 } catch (IOException e) {
1106 return new HashMap<>();
1107 }
1108 }
1109
1110 /**
1111 * Get mutable map of all tags
1112 *
1113 * @return mutable map of all tags; key is short tag name ("v1.0") and value
1114 * of the entry contains the ref with the full tag name
1115 * ("refs/tags/v1.0").
1116 * @deprecated use {@code getRefDatabase().getRefsByPrefix(R_TAGS)} instead
1117 */
1118 @Deprecated
1119 @NonNull
1120 public Map<String, Ref> getTags() {
1121 try {
1122 return getRefDatabase().getRefs(Constants.R_TAGS);
1123 } catch (IOException e) {
1124 return new HashMap<>();
1125 }
1126 }
1127
1128 /**
1129 * Peel a possibly unpeeled reference to an annotated tag.
1130 * <p>
1131 * If the ref cannot be peeled (as it does not refer to an annotated tag)
1132 * the peeled id stays null, but {@link org.eclipse.jgit.lib.Ref#isPeeled()}
1133 * will be true.
1134 *
1135 * @param ref
1136 * The ref to peel
1137 * @return <code>ref</code> if <code>ref.isPeeled()</code> is true; else a
1138 * new Ref object representing the same data as Ref, but isPeeled()
1139 * will be true and getPeeledObjectId will contain the peeled object
1140 * (or null).
1141 * @deprecated use {@code getRefDatabase().peel(ref)} instead.
1142 */
1143 @Deprecated
1144 @NonNull
1145 public Ref peel(Ref ref) {
1146 try {
1147 return getRefDatabase().peel(ref);
1148 } catch (IOException e) {
1149 // Historical accident; if the reference cannot be peeled due
1150 // to some sort of repository access problem we claim that the
1151 // same as if the reference was not an annotated tag.
1152 return ref;
1153 }
1154 }
1155
1156 /**
1157 * Get a map with all objects referenced by a peeled ref.
1158 *
1159 * @return a map with all objects referenced by a peeled ref.
1160 */
1161 @NonNull
1162 public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() {
1163 Map<String, Ref> allRefs = getAllRefs();
1164 Map<AnyObjectId, Set<Ref>> ret = new HashMap<>(allRefs.size());
1165 for (Ref ref : allRefs.values()) {
1166 ref = peel(ref);
1167 AnyObjectId target = ref.getPeeledObjectId();
1168 if (target == null)
1169 target = ref.getObjectId();
1170 // We assume most Sets here are singletons
1171 Set<Ref> oset = ret.put(target, Collections.singleton(ref));
1172 if (oset != null) {
1173 // that was not the case (rare)
1174 if (oset.size() == 1) {
1175 // Was a read-only singleton, we must copy to a new Set
1176 oset = new HashSet<>(oset);
1177 }
1178 ret.put(target, oset);
1179 oset.add(ref);
1180 }
1181 }
1182 return ret;
1183 }
1184
1185 /**
1186 * Get the index file location or {@code null} if repository isn't local.
1187 *
1188 * @return the index file location or {@code null} if repository isn't
1189 * local.
1190 * @throws org.eclipse.jgit.errors.NoWorkTreeException
1191 * if this is bare, which implies it has no working directory.
1192 * See {@link #isBare()}.
1193 */
1194 @NonNull
1195 public File getIndexFile() throws NoWorkTreeException {
1196 if (isBare())
1197 throw new NoWorkTreeException();
1198 return indexFile;
1199 }
1200
1201 /**
1202 * Locate a reference to a commit and immediately parse its content.
1203 * <p>
1204 * This method only returns successfully if the commit object exists,
1205 * is verified to be a commit, and was parsed without error.
1206 *
1207 * @param id
1208 * name of the commit object.
1209 * @return reference to the commit object. Never null.
1210 * @throws org.eclipse.jgit.errors.MissingObjectException
1211 * the supplied commit does not exist.
1212 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
1213 * the supplied id is not a commit or an annotated tag.
1214 * @throws java.io.IOException
1215 * a pack file or loose object could not be read.
1216 * @since 4.8
1217 */
1218 public RevCommit parseCommit(AnyObjectId id) throws IncorrectObjectTypeException,
1219 IOException, MissingObjectException {
1220 if (id instanceof RevCommit && ((RevCommit) id).getRawBuffer() != null) {
1221 return (RevCommit) id;
1222 }
1223 try (RevWalk walk = new RevWalk(this)) {
1224 return walk.parseCommit(id);
1225 }
1226 }
1227
1228 /**
1229 * Create a new in-core index representation and read an index from disk.
1230 * <p>
1231 * The new index will be read before it is returned to the caller. Read
1232 * failures are reported as exceptions and therefore prevent the method from
1233 * returning a partially populated index.
1234 *
1235 * @return a cache representing the contents of the specified index file (if
1236 * it exists) or an empty cache if the file does not exist.
1237 * @throws org.eclipse.jgit.errors.NoWorkTreeException
1238 * if this is bare, which implies it has no working directory.
1239 * See {@link #isBare()}.
1240 * @throws java.io.IOException
1241 * the index file is present but could not be read.
1242 * @throws org.eclipse.jgit.errors.CorruptObjectException
1243 * the index file is using a format or extension that this
1244 * library does not support.
1245 */
1246 @NonNull
1247 public DirCache readDirCache() throws NoWorkTreeException,
1248 CorruptObjectException, IOException {
1249 return DirCache.read(this);
1250 }
1251
1252 /**
1253 * Create a new in-core index representation, lock it, and read from disk.
1254 * <p>
1255 * The new index will be locked and then read before it is returned to the
1256 * caller. Read failures are reported as exceptions and therefore prevent
1257 * the method from returning a partially populated index.
1258 *
1259 * @return a cache representing the contents of the specified index file (if
1260 * it exists) or an empty cache if the file does not exist.
1261 * @throws org.eclipse.jgit.errors.NoWorkTreeException
1262 * if this is bare, which implies it has no working directory.
1263 * See {@link #isBare()}.
1264 * @throws java.io.IOException
1265 * the index file is present but could not be read, or the lock
1266 * could not be obtained.
1267 * @throws org.eclipse.jgit.errors.CorruptObjectException
1268 * the index file is using a format or extension that this
1269 * library does not support.
1270 */
1271 @NonNull
1272 public DirCache lockDirCache() throws NoWorkTreeException,
1273 CorruptObjectException, IOException {
1274 // we want DirCache to inform us so that we can inform registered
1275 // listeners about index changes
1276 IndexChangedListener l = new IndexChangedListener() {
1277 @Override
1278 public void onIndexChanged(IndexChangedEvent event) {
1279 notifyIndexChanged(true);
1280 }
1281 };
1282 return DirCache.lock(this, l);
1283 }
1284
1285 /**
1286 * Get the repository state
1287 *
1288 * @return the repository state
1289 */
1290 @NonNull
1291 public RepositoryState getRepositoryState() {
1292 if (isBare() || getDirectory() == null)
1293 return RepositoryState.BARE;
1294
1295 // Pre Git-1.6 logic
1296 if (new File(getWorkTree(), ".dotest").exists()) //$NON-NLS-1$
1297 return RepositoryState.REBASING;
1298 if (new File(getDirectory(), ".dotest-merge").exists()) //$NON-NLS-1$
1299 return RepositoryState.REBASING_INTERACTIVE;
1300
1301 // From 1.6 onwards
1302 if (new File(getDirectory(),"rebase-apply/rebasing").exists()) //$NON-NLS-1$
1303 return RepositoryState.REBASING_REBASING;
1304 if (new File(getDirectory(),"rebase-apply/applying").exists()) //$NON-NLS-1$
1305 return RepositoryState.APPLY;
1306 if (new File(getDirectory(),"rebase-apply").exists()) //$NON-NLS-1$
1307 return RepositoryState.REBASING;
1308
1309 if (new File(getDirectory(),"rebase-merge/interactive").exists()) //$NON-NLS-1$
1310 return RepositoryState.REBASING_INTERACTIVE;
1311 if (new File(getDirectory(),"rebase-merge").exists()) //$NON-NLS-1$
1312 return RepositoryState.REBASING_MERGE;
1313
1314 // Both versions
1315 if (new File(getDirectory(), Constants.MERGE_HEAD).exists()) {
1316 // we are merging - now check whether we have unmerged paths
1317 try {
1318 if (!readDirCache().hasUnmergedPaths()) {
1319 // no unmerged paths -> return the MERGING_RESOLVED state
1320 return RepositoryState.MERGING_RESOLVED;
1321 }
1322 } catch (IOException e) {
1323 // Can't decide whether unmerged paths exists. Return
1324 // MERGING state to be on the safe side (in state MERGING
1325 // you are not allow to do anything)
1326 }
1327 return RepositoryState.MERGING;
1328 }
1329
1330 if (new File(getDirectory(), "BISECT_LOG").exists()) //$NON-NLS-1$
1331 return RepositoryState.BISECTING;
1332
1333 if (new File(getDirectory(), Constants.CHERRY_PICK_HEAD).exists()) {
1334 try {
1335 if (!readDirCache().hasUnmergedPaths()) {
1336 // no unmerged paths
1337 return RepositoryState.CHERRY_PICKING_RESOLVED;
1338 }
1339 } catch (IOException e) {
1340 // fall through to CHERRY_PICKING
1341 }
1342
1343 return RepositoryState.CHERRY_PICKING;
1344 }
1345
1346 if (new File(getDirectory(), Constants.REVERT_HEAD).exists()) {
1347 try {
1348 if (!readDirCache().hasUnmergedPaths()) {
1349 // no unmerged paths
1350 return RepositoryState.REVERTING_RESOLVED;
1351 }
1352 } catch (IOException e) {
1353 // fall through to REVERTING
1354 }
1355
1356 return RepositoryState.REVERTING;
1357 }
1358
1359 return RepositoryState.SAFE;
1360 }
1361
1362 /**
1363 * Check validity of a ref name. It must not contain character that has
1364 * a special meaning in a Git object reference expression. Some other
1365 * dangerous characters are also excluded.
1366 *
1367 * For portability reasons '\' is excluded
1368 *
1369 * @param refName a {@link java.lang.String} object.
1370 * @return true if refName is a valid ref name
1371 */
1372 public static boolean isValidRefName(String refName) {
1373 final int len = refName.length();
1374 if (len == 0) {
1375 return false;
1376 }
1377 if (refName.endsWith(LOCK_SUFFIX)) {
1378 return false;
1379 }
1380
1381 // Refs may be stored as loose files so invalid paths
1382 // on the local system must also be invalid refs.
1383 try {
1384 SystemReader.getInstance().checkPath(refName);
1385 } catch (CorruptObjectException e) {
1386 return false;
1387 }
1388
1389 int components = 1;
1390 char p = '\0';
1391 for (int i = 0; i < len; i++) {
1392 final char c = refName.charAt(i);
1393 if (c <= ' ')
1394 return false;
1395 switch (c) {
1396 case '.':
1397 switch (p) {
1398 case '\0': case '/': case '.':
1399 return false;
1400 }
1401 if (i == len -1)
1402 return false;
1403 break;
1404 case '/':
1405 if (i == 0 || i == len - 1)
1406 return false;
1407 if (p == '/')
1408 return false;
1409 components++;
1410 break;
1411 case '{':
1412 if (p == '@')
1413 return false;
1414 break;
1415 case '~': case '^': case ':':
1416 case '?': case '[': case '*':
1417 case '\\':
1418 case '\u007F':
1419 return false;
1420 }
1421 p = c;
1422 }
1423 return components > 1;
1424 }
1425
1426 /**
1427 * Normalizes the passed branch name into a possible valid branch name. The
1428 * validity of the returned name should be checked by a subsequent call to
1429 * {@link #isValidRefName(String)}.
1430 * <p>
1431 * Future implementations of this method could be more restrictive or more
1432 * lenient about the validity of specific characters in the returned name.
1433 * <p>
1434 * The current implementation returns the trimmed input string if this is
1435 * already a valid branch name. Otherwise it returns a trimmed string with
1436 * special characters not allowed by {@link #isValidRefName(String)}
1437 * replaced by hyphens ('-') and blanks replaced by underscores ('_').
1438 * Leading and trailing slashes, dots, hyphens, and underscores are removed.
1439 *
1440 * @param name
1441 * to normalize
1442 * @return The normalized name or an empty String if it is {@code null} or
1443 * empty.
1444 * @since 4.7
1445 * @see #isValidRefName(String)
1446 */
1447 public static String normalizeBranchName(String name) {
1448 if (name == null || name.isEmpty()) {
1449 return ""; //$NON-NLS-1$
1450 }
1451 String result = name.trim();
1452 String fullName = result.startsWith(Constants.R_HEADS) ? result
1453 : Constants.R_HEADS + result;
1454 if (isValidRefName(fullName)) {
1455 return result;
1456 }
1457
1458 // All Unicode blanks to underscore
1459 result = result.replaceAll("(?:\\h|\\v)+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
1460 StringBuilder b = new StringBuilder();
1461 char p = '/';
1462 for (int i = 0, len = result.length(); i < len; i++) {
1463 char c = result.charAt(i);
1464 if (c < ' ' || c == 127) {
1465 continue;
1466 }
1467 // Substitute a dash for problematic characters
1468 switch (c) {
1469 case '\\':
1470 case '^':
1471 case '~':
1472 case ':':
1473 case '?':
1474 case '*':
1475 case '[':
1476 case '@':
1477 case '<':
1478 case '>':
1479 case '|':
1480 case '"':
1481 c = '-';
1482 break;
1483 default:
1484 break;
1485 }
1486 // Collapse multiple slashes, dashes, dots, underscores, and omit
1487 // dashes, dots, and underscores following a slash.
1488 switch (c) {
1489 case '/':
1490 if (p == '/') {
1491 continue;
1492 }
1493 p = '/';
1494 break;
1495 case '.':
1496 case '_':
1497 case '-':
1498 if (p == '/' || p == '-') {
1499 continue;
1500 }
1501 p = '-';
1502 break;
1503 default:
1504 p = c;
1505 break;
1506 }
1507 b.append(c);
1508 }
1509 // Strip trailing special characters, and avoid the .lock extension
1510 result = b.toString().replaceFirst("[/_.-]+$", "") //$NON-NLS-1$ //$NON-NLS-2$
1511 .replaceAll("\\.lock($|/)", "_lock$1"); //$NON-NLS-1$ //$NON-NLS-2$
1512 return FORBIDDEN_BRANCH_NAME_COMPONENTS.matcher(result)
1513 .replaceAll("$1+$2$3"); //$NON-NLS-1$
1514 }
1515
1516 /**
1517 * Strip work dir and return normalized repository path.
1518 *
1519 * @param workDir
1520 * Work dir
1521 * @param file
1522 * File whose path shall be stripped of its workdir
1523 * @return normalized repository relative path or the empty string if the
1524 * file is not relative to the work directory.
1525 */
1526 @NonNull
1527 public static String stripWorkDir(File workDir, File file) {
1528 final String filePath = file.getPath();
1529 final String workDirPath = workDir.getPath();
1530
1531 if (filePath.length() <= workDirPath.length() ||
1532 filePath.charAt(workDirPath.length()) != File.separatorChar ||
1533 !filePath.startsWith(workDirPath)) {
1534 File absWd = workDir.isAbsolute() ? workDir : workDir.getAbsoluteFile();
1535 File absFile = file.isAbsolute() ? file : file.getAbsoluteFile();
1536 if (absWd == workDir && absFile == file)
1537 return ""; //$NON-NLS-1$
1538 return stripWorkDir(absWd, absFile);
1539 }
1540
1541 String relName = filePath.substring(workDirPath.length() + 1);
1542 if (File.separatorChar != '/')
1543 relName = relName.replace(File.separatorChar, '/');
1544 return relName;
1545 }
1546
1547 /**
1548 * Whether this repository is bare
1549 *
1550 * @return true if this is bare, which implies it has no working directory.
1551 */
1552 public boolean isBare() {
1553 return workTree == null;
1554 }
1555
1556 /**
1557 * Get the root directory of the working tree, where files are checked out
1558 * for viewing and editing.
1559 *
1560 * @return the root directory of the working tree, where files are checked
1561 * out for viewing and editing.
1562 * @throws org.eclipse.jgit.errors.NoWorkTreeException
1563 * if this is bare, which implies it has no working directory.
1564 * See {@link #isBare()}.
1565 */
1566 @NonNull
1567 public File getWorkTree() throws NoWorkTreeException {
1568 if (isBare())
1569 throw new NoWorkTreeException();
1570 return workTree;
1571 }
1572
1573 /**
1574 * Force a scan for changed refs. Fires an IndexChangedEvent(false) if
1575 * changes are detected.
1576 *
1577 * @throws java.io.IOException
1578 */
1579 public abstract void scanForRepoChanges() throws IOException;
1580
1581 /**
1582 * Notify that the index changed by firing an IndexChangedEvent.
1583 *
1584 * @param internal
1585 * {@code true} if the index was changed by the same
1586 * JGit process
1587 * @since 5.0
1588 */
1589 public abstract void notifyIndexChanged(boolean internal);
1590
1591 /**
1592 * Get a shortened more user friendly ref name
1593 *
1594 * @param refName
1595 * a {@link java.lang.String} object.
1596 * @return a more user friendly ref name
1597 */
1598 @NonNull
1599 public static String shortenRefName(String refName) {
1600 if (refName.startsWith(Constants.R_HEADS))
1601 return refName.substring(Constants.R_HEADS.length());
1602 if (refName.startsWith(Constants.R_TAGS))
1603 return refName.substring(Constants.R_TAGS.length());
1604 if (refName.startsWith(Constants.R_REMOTES))
1605 return refName.substring(Constants.R_REMOTES.length());
1606 return refName;
1607 }
1608
1609 /**
1610 * Get a shortened more user friendly remote tracking branch name
1611 *
1612 * @param refName
1613 * a {@link java.lang.String} object.
1614 * @return the remote branch name part of <code>refName</code>, i.e. without
1615 * the <code>refs/remotes/<remote></code> prefix, if
1616 * <code>refName</code> represents a remote tracking branch;
1617 * otherwise {@code null}.
1618 * @since 3.4
1619 */
1620 @Nullable
1621 public String shortenRemoteBranchName(String refName) {
1622 for (String remote : getRemoteNames()) {
1623 String remotePrefix = Constants.R_REMOTES + remote + "/"; //$NON-NLS-1$
1624 if (refName.startsWith(remotePrefix))
1625 return refName.substring(remotePrefix.length());
1626 }
1627 return null;
1628 }
1629
1630 /**
1631 * Get remote name
1632 *
1633 * @param refName
1634 * a {@link java.lang.String} object.
1635 * @return the remote name part of <code>refName</code>, i.e. without the
1636 * <code>refs/remotes/<remote></code> prefix, if
1637 * <code>refName</code> represents a remote tracking branch;
1638 * otherwise {@code null}.
1639 * @since 3.4
1640 */
1641 @Nullable
1642 public String getRemoteName(String refName) {
1643 for (String remote : getRemoteNames()) {
1644 String remotePrefix = Constants.R_REMOTES + remote + "/"; //$NON-NLS-1$
1645 if (refName.startsWith(remotePrefix))
1646 return remote;
1647 }
1648 return null;
1649 }
1650
1651 /**
1652 * Read the {@code GIT_DIR/description} file for gitweb.
1653 *
1654 * @return description text; null if no description has been configured.
1655 * @throws java.io.IOException
1656 * description cannot be accessed.
1657 * @since 4.6
1658 */
1659 @Nullable
1660 public String getGitwebDescription() throws IOException {
1661 return null;
1662 }
1663
1664 /**
1665 * Set the {@code GIT_DIR/description} file for gitweb.
1666 *
1667 * @param description
1668 * new description; null to clear the description.
1669 * @throws java.io.IOException
1670 * description cannot be persisted.
1671 * @since 4.6
1672 */
1673 public void setGitwebDescription(@Nullable String description)
1674 throws IOException {
1675 throw new IOException(JGitText.get().unsupportedRepositoryDescription);
1676 }
1677
1678 /**
1679 * Get the reflog reader
1680 *
1681 * @param refName
1682 * a {@link java.lang.String} object.
1683 * @return a {@link org.eclipse.jgit.lib.ReflogReader} for the supplied
1684 * refname, or {@code null} if the named ref does not exist.
1685 * @throws java.io.IOException
1686 * the ref could not be accessed.
1687 * @since 3.0
1688 */
1689 @Nullable
1690 public abstract ReflogReader getReflogReader(String refName)
1691 throws IOException;
1692
1693 /**
1694 * Return the information stored in the file $GIT_DIR/MERGE_MSG. In this
1695 * file operations triggering a merge will store a template for the commit
1696 * message of the merge commit.
1697 *
1698 * @return a String containing the content of the MERGE_MSG file or
1699 * {@code null} if this file doesn't exist
1700 * @throws java.io.IOException
1701 * @throws org.eclipse.jgit.errors.NoWorkTreeException
1702 * if this is bare, which implies it has no working directory.
1703 * See {@link #isBare()}.
1704 */
1705 @Nullable
1706 public String readMergeCommitMsg() throws IOException, NoWorkTreeException {
1707 return readCommitMsgFile(Constants.MERGE_MSG);
1708 }
1709
1710 /**
1711 * Write new content to the file $GIT_DIR/MERGE_MSG. In this file operations
1712 * triggering a merge will store a template for the commit message of the
1713 * merge commit. If <code>null</code> is specified as message the file will
1714 * be deleted.
1715 *
1716 * @param msg
1717 * the message which should be written or <code>null</code> to
1718 * delete the file
1719 * @throws java.io.IOException
1720 */
1721 public void writeMergeCommitMsg(String msg) throws IOException {
1722 File mergeMsgFile = new File(gitDir, Constants.MERGE_MSG);
1723 writeCommitMsg(mergeMsgFile, msg);
1724 }
1725
1726 /**
1727 * Return the information stored in the file $GIT_DIR/COMMIT_EDITMSG. In
1728 * this file hooks triggered by an operation may read or modify the current
1729 * commit message.
1730 *
1731 * @return a String containing the content of the COMMIT_EDITMSG file or
1732 * {@code null} if this file doesn't exist
1733 * @throws java.io.IOException
1734 * @throws org.eclipse.jgit.errors.NoWorkTreeException
1735 * if this is bare, which implies it has no working directory.
1736 * See {@link #isBare()}.
1737 * @since 4.0
1738 */
1739 @Nullable
1740 public String readCommitEditMsg() throws IOException, NoWorkTreeException {
1741 return readCommitMsgFile(Constants.COMMIT_EDITMSG);
1742 }
1743
1744 /**
1745 * Write new content to the file $GIT_DIR/COMMIT_EDITMSG. In this file hooks
1746 * triggered by an operation may read or modify the current commit message.
1747 * If {@code null} is specified as message the file will be deleted.
1748 *
1749 * @param msg
1750 * the message which should be written or {@code null} to delete
1751 * the file
1752 * @throws java.io.IOException
1753 * @since 4.0
1754 */
1755 public void writeCommitEditMsg(String msg) throws IOException {
1756 File commiEditMsgFile = new File(gitDir, Constants.COMMIT_EDITMSG);
1757 writeCommitMsg(commiEditMsgFile, msg);
1758 }
1759
1760 /**
1761 * Return the information stored in the file $GIT_DIR/MERGE_HEAD. In this
1762 * file operations triggering a merge will store the IDs of all heads which
1763 * should be merged together with HEAD.
1764 *
1765 * @return a list of commits which IDs are listed in the MERGE_HEAD file or
1766 * {@code null} if this file doesn't exist. Also if the file exists
1767 * but is empty {@code null} will be returned
1768 * @throws java.io.IOException
1769 * @throws org.eclipse.jgit.errors.NoWorkTreeException
1770 * if this is bare, which implies it has no working directory.
1771 * See {@link #isBare()}.
1772 */
1773 @Nullable
1774 public List<ObjectId> readMergeHeads() throws IOException, NoWorkTreeException {
1775 if (isBare() || getDirectory() == null)
1776 throw new NoWorkTreeException();
1777
1778 byte[] raw = readGitDirectoryFile(Constants.MERGE_HEAD);
1779 if (raw == null)
1780 return null;
1781
1782 LinkedList<ObjectId> heads = new LinkedList<>();
1783 for (int p = 0; p < raw.length;) {
1784 heads.add(ObjectId.fromString(raw, p));
1785 p = RawParseUtils
1786 .nextLF(raw, p + Constants.OBJECT_ID_STRING_LENGTH);
1787 }
1788 return heads;
1789 }
1790
1791 /**
1792 * Write new merge-heads into $GIT_DIR/MERGE_HEAD. In this file operations
1793 * triggering a merge will store the IDs of all heads which should be merged
1794 * together with HEAD. If <code>null</code> is specified as list of commits
1795 * the file will be deleted
1796 *
1797 * @param heads
1798 * a list of commits which IDs should be written to
1799 * $GIT_DIR/MERGE_HEAD or <code>null</code> to delete the file
1800 * @throws java.io.IOException
1801 */
1802 public void writeMergeHeads(List<? extends ObjectId> heads) throws IOException {
1803 writeHeadsFile(heads, Constants.MERGE_HEAD);
1804 }
1805
1806 /**
1807 * Return the information stored in the file $GIT_DIR/CHERRY_PICK_HEAD.
1808 *
1809 * @return object id from CHERRY_PICK_HEAD file or {@code null} if this file
1810 * doesn't exist. Also if the file exists but is empty {@code null}
1811 * will be returned
1812 * @throws java.io.IOException
1813 * @throws org.eclipse.jgit.errors.NoWorkTreeException
1814 * if this is bare, which implies it has no working directory.
1815 * See {@link #isBare()}.
1816 */
1817 @Nullable
1818 public ObjectId readCherryPickHead() throws IOException,
1819 NoWorkTreeException {
1820 if (isBare() || getDirectory() == null)
1821 throw new NoWorkTreeException();
1822
1823 byte[] raw = readGitDirectoryFile(Constants.CHERRY_PICK_HEAD);
1824 if (raw == null)
1825 return null;
1826
1827 return ObjectId.fromString(raw, 0);
1828 }
1829
1830 /**
1831 * Return the information stored in the file $GIT_DIR/REVERT_HEAD.
1832 *
1833 * @return object id from REVERT_HEAD file or {@code null} if this file
1834 * doesn't exist. Also if the file exists but is empty {@code null}
1835 * will be returned
1836 * @throws java.io.IOException
1837 * @throws org.eclipse.jgit.errors.NoWorkTreeException
1838 * if this is bare, which implies it has no working directory.
1839 * See {@link #isBare()}.
1840 */
1841 @Nullable
1842 public ObjectId readRevertHead() throws IOException, NoWorkTreeException {
1843 if (isBare() || getDirectory() == null)
1844 throw new NoWorkTreeException();
1845
1846 byte[] raw = readGitDirectoryFile(Constants.REVERT_HEAD);
1847 if (raw == null)
1848 return null;
1849 return ObjectId.fromString(raw, 0);
1850 }
1851
1852 /**
1853 * Write cherry pick commit into $GIT_DIR/CHERRY_PICK_HEAD. This is used in
1854 * case of conflicts to store the cherry which was tried to be picked.
1855 *
1856 * @param head
1857 * an object id of the cherry commit or <code>null</code> to
1858 * delete the file
1859 * @throws java.io.IOException
1860 */
1861 public void writeCherryPickHead(ObjectId head) throws IOException {
1862 List<ObjectId> heads = (head != null) ? Collections.singletonList(head)
1863 : null;
1864 writeHeadsFile(heads, Constants.CHERRY_PICK_HEAD);
1865 }
1866
1867 /**
1868 * Write revert commit into $GIT_DIR/REVERT_HEAD. This is used in case of
1869 * conflicts to store the revert which was tried to be picked.
1870 *
1871 * @param head
1872 * an object id of the revert commit or <code>null</code> to
1873 * delete the file
1874 * @throws java.io.IOException
1875 */
1876 public void writeRevertHead(ObjectId head) throws IOException {
1877 List<ObjectId> heads = (head != null) ? Collections.singletonList(head)
1878 : null;
1879 writeHeadsFile(heads, Constants.REVERT_HEAD);
1880 }
1881
1882 /**
1883 * Write original HEAD commit into $GIT_DIR/ORIG_HEAD.
1884 *
1885 * @param head
1886 * an object id of the original HEAD commit or <code>null</code>
1887 * to delete the file
1888 * @throws java.io.IOException
1889 */
1890 public void writeOrigHead(ObjectId head) throws IOException {
1891 List<ObjectId> heads = head != null ? Collections.singletonList(head)
1892 : null;
1893 writeHeadsFile(heads, Constants.ORIG_HEAD);
1894 }
1895
1896 /**
1897 * Return the information stored in the file $GIT_DIR/ORIG_HEAD.
1898 *
1899 * @return object id from ORIG_HEAD file or {@code null} if this file
1900 * doesn't exist. Also if the file exists but is empty {@code null}
1901 * will be returned
1902 * @throws java.io.IOException
1903 * @throws org.eclipse.jgit.errors.NoWorkTreeException
1904 * if this is bare, which implies it has no working directory.
1905 * See {@link #isBare()}.
1906 */
1907 @Nullable
1908 public ObjectId readOrigHead() throws IOException, NoWorkTreeException {
1909 if (isBare() || getDirectory() == null)
1910 throw new NoWorkTreeException();
1911
1912 byte[] raw = readGitDirectoryFile(Constants.ORIG_HEAD);
1913 return raw != null ? ObjectId.fromString(raw, 0) : null;
1914 }
1915
1916 /**
1917 * Return the information stored in the file $GIT_DIR/SQUASH_MSG. In this
1918 * file operations triggering a squashed merge will store a template for the
1919 * commit message of the squash commit.
1920 *
1921 * @return a String containing the content of the SQUASH_MSG file or
1922 * {@code null} if this file doesn't exist
1923 * @throws java.io.IOException
1924 * @throws NoWorkTreeException
1925 * if this is bare, which implies it has no working directory.
1926 * See {@link #isBare()}.
1927 */
1928 @Nullable
1929 public String readSquashCommitMsg() throws IOException {
1930 return readCommitMsgFile(Constants.SQUASH_MSG);
1931 }
1932
1933 /**
1934 * Write new content to the file $GIT_DIR/SQUASH_MSG. In this file
1935 * operations triggering a squashed merge will store a template for the
1936 * commit message of the squash commit. If <code>null</code> is specified as
1937 * message the file will be deleted.
1938 *
1939 * @param msg
1940 * the message which should be written or <code>null</code> to
1941 * delete the file
1942 * @throws java.io.IOException
1943 */
1944 public void writeSquashCommitMsg(String msg) throws IOException {
1945 File squashMsgFile = new File(gitDir, Constants.SQUASH_MSG);
1946 writeCommitMsg(squashMsgFile, msg);
1947 }
1948
1949 @Nullable
1950 private String readCommitMsgFile(String msgFilename) throws IOException {
1951 if (isBare() || getDirectory() == null)
1952 throw new NoWorkTreeException();
1953
1954 File mergeMsgFile = new File(getDirectory(), msgFilename);
1955 try {
1956 return RawParseUtils.decode(IO.readFully(mergeMsgFile));
1957 } catch (FileNotFoundException e) {
1958 if (mergeMsgFile.exists()) {
1959 throw e;
1960 }
1961 // the file has disappeared in the meantime ignore it
1962 return null;
1963 }
1964 }
1965
1966 private void writeCommitMsg(File msgFile, String msg) throws IOException {
1967 if (msg != null) {
1968 try (FileOutputStream fos = new FileOutputStream(msgFile)) {
1969 fos.write(msg.getBytes(UTF_8));
1970 }
1971 } else {
1972 FileUtils.delete(msgFile, FileUtils.SKIP_MISSING);
1973 }
1974 }
1975
1976 /**
1977 * Read a file from the git directory.
1978 *
1979 * @param filename
1980 * @return the raw contents or {@code null} if the file doesn't exist or is
1981 * empty
1982 * @throws IOException
1983 */
1984 @Nullable
1985 private byte[] readGitDirectoryFile(String filename) throws IOException {
1986 File file = new File(getDirectory(), filename);
1987 try {
1988 byte[] raw = IO.readFully(file);
1989 return raw.length > 0 ? raw : null;
1990 } catch (FileNotFoundException notFound) {
1991 if (file.exists()) {
1992 throw notFound;
1993 }
1994 return null;
1995 }
1996 }
1997
1998 /**
1999 * Write the given heads to a file in the git directory.
2000 *
2001 * @param heads
2002 * a list of object ids to write or null if the file should be
2003 * deleted.
2004 * @param filename
2005 * @throws FileNotFoundException
2006 * @throws IOException
2007 */
2008 private void writeHeadsFile(List<? extends ObjectId> heads, String filename)
2009 throws FileNotFoundException, IOException {
2010 File headsFile = new File(getDirectory(), filename);
2011 if (heads != null) {
2012 try (OutputStream bos = new BufferedOutputStream(
2013 new FileOutputStream(headsFile))) {
2014 for (ObjectId id : heads) {
2015 id.copyTo(bos);
2016 bos.write('\n');
2017 }
2018 }
2019 } else {
2020 FileUtils.delete(headsFile, FileUtils.SKIP_MISSING);
2021 }
2022 }
2023
2024 /**
2025 * Read a file formatted like the git-rebase-todo file. The "done" file is
2026 * also formatted like the git-rebase-todo file. These files can be found in
2027 * .git/rebase-merge/ or .git/rebase-append/ folders.
2028 *
2029 * @param path
2030 * path to the file relative to the repository's git-dir. E.g.
2031 * "rebase-merge/git-rebase-todo" or "rebase-append/done"
2032 * @param includeComments
2033 * <code>true</code> if also comments should be reported
2034 * @return the list of steps
2035 * @throws java.io.IOException
2036 * @since 3.2
2037 */
2038 @NonNull
2039 public List<RebaseTodoLine> readRebaseTodo(String path,
2040 boolean includeComments)
2041 throws IOException {
2042 return new RebaseTodoFile(this).readRebaseTodo(path, includeComments);
2043 }
2044
2045 /**
2046 * Write a file formatted like a git-rebase-todo file.
2047 *
2048 * @param path
2049 * path to the file relative to the repository's git-dir. E.g.
2050 * "rebase-merge/git-rebase-todo" or "rebase-append/done"
2051 * @param steps
2052 * the steps to be written
2053 * @param append
2054 * whether to append to an existing file or to write a new file
2055 * @throws java.io.IOException
2056 * @since 3.2
2057 */
2058 public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps,
2059 boolean append)
2060 throws IOException {
2061 new RebaseTodoFile(this).writeRebaseTodoFile(path, steps, append);
2062 }
2063
2064 /**
2065 * Get the names of all known remotes
2066 *
2067 * @return the names of all known remotes
2068 * @since 3.4
2069 */
2070 @NonNull
2071 public Set<String> getRemoteNames() {
2072 return getConfig()
2073 .getSubsections(ConfigConstants.CONFIG_REMOTE_SECTION);
2074 }
2075
2076 /**
2077 * Check whether any housekeeping is required; if yes, run garbage
2078 * collection; if not, exit without performing any work. Some JGit commands
2079 * run autoGC after performing operations that could create many loose
2080 * objects.
2081 * <p>
2082 * Currently this option is supported for repositories of type
2083 * {@code FileRepository} only. See
2084 * {@link org.eclipse.jgit.internal.storage.file.GC#setAuto(boolean)} for
2085 * configuration details.
2086 *
2087 * @param monitor
2088 * to report progress
2089 * @since 4.6
2090 */
2091 public void autoGC(ProgressMonitor monitor) {
2092 // default does nothing
2093 }
2094 }