Merge branch 'stable-4.8' into stable-4.9
* stable-4.8:
Prepare 4.7.9-SNAPSHOT builds
JGit v4.7.8.201903121755-r
Prepare 4.5.7-SNAPSHOT builds
JGit v4.5.6.201903121547-r
Check for packfile validity and fd before reading
Move throw of PackInvalidException outside the catch
Use FileSnapshot to get lastModified on PackFile
Include size when comparing FileSnapshot
Do not reuse packfiles when changed on filesystem
Silence API warnings for new API introduced for fixes
Change-Id: I9a47153831f8eb10d3cd91b4157cf45385e5b13a
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/.gitignore b/.gitignore
index 963b8a4..3679a33 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,6 @@
/.project
/target
+.DS_Store
infer-out
-bazel-bin
-bazel-genfiles
-bazel-jgit
-bazel-out
-bazel-testlogs
+bazel-*
*~
diff --git a/Documentation/technical/reftable.md b/Documentation/technical/reftable.md
new file mode 100644
index 0000000..47c61a3
--- /dev/null
+++ b/Documentation/technical/reftable.md
@@ -0,0 +1,955 @@
+# reftable
+
+[TOC]
+
+## Overview
+
+### Problem statement
+
+Some repositories contain a lot of references (e.g. android at 866k,
+rails at 31k). The existing packed-refs format takes up a lot of
+space (e.g. 62M), and does not scale with additional references.
+Lookup of a single reference requires linearly scanning the file.
+
+Atomic pushes modifying multiple references require copying the
+entire packed-refs file, which can be a considerable amount of data
+moved (e.g. 62M in, 62M out) for even small transactions (2 refs
+modified).
+
+Repositories with many loose references occupy a large number of disk
+blocks from the local file system, as each reference is its own file
+storing 41 bytes (and another file for the corresponding reflog).
+This negatively affects the number of inodes available when a large
+number of repositories are stored on the same filesystem. Readers can
+be penalized due to the larger number of syscalls required to traverse
+and read the `$GIT_DIR/refs` directory.
+
+### Objectives
+
+- Near constant time lookup for any single reference, even when the
+ repository is cold and not in process or kernel cache.
+- Near constant time verification if a SHA-1 is referred to by at
+ least one reference (for allow-tip-sha1-in-want).
+- Efficient lookup of an entire namespace, such as `refs/tags/`.
+- Support atomic push with `O(size_of_update)` operations.
+- Combine reflog storage with ref storage for small transactions.
+- Separate reflog storage for base refs and historical logs.
+
+### Description
+
+A reftable file is a portable binary file format customized for
+reference storage. References are sorted, enabling linear scans,
+binary search lookup, and range scans.
+
+Storage in the file is organized into variable sized blocks. Prefix
+compression is used within a single block to reduce disk space. Block
+size and alignment is tunable by the writer.
+
+### Performance
+
+Space used, packed-refs vs. reftable:
+
+repository | packed-refs | reftable | % original | avg ref | avg obj
+-----------|------------:|---------:|-----------:|---------:|--------:
+android | 62.2 M | 36.1 M | 58.0% | 33 bytes | 5 bytes
+rails | 1.8 M | 1.1 M | 57.7% | 29 bytes | 4 bytes
+git | 78.7 K | 48.1 K | 61.0% | 50 bytes | 4 bytes
+git (heads)| 332 b | 269 b | 81.0% | 33 bytes | 0 bytes
+
+Scan (read 866k refs), by reference name lookup (single ref from 866k
+refs), and by SHA-1 lookup (refs with that SHA-1, from 866k refs):
+
+format | cache | scan | by name | by SHA-1
+------------|------:|--------:|---------------:|---------------:
+packed-refs | cold | 402 ms | 409,660.1 usec | 412,535.8 usec
+packed-refs | hot | | 6,844.6 usec | 20,110.1 usec
+reftable | cold | 112 ms | 33.9 usec | 323.2 usec
+reftable | hot | | 20.2 usec | 320.8 usec
+
+Space used for 149,932 log entries for 43,061 refs,
+reflog vs. reftable:
+
+format | size | avg entry
+--------------|------:|-----------:
+$GIT_DIR/logs | 173 M | 1209 bytes
+reftable | 5 M | 37 bytes
+
+## Details
+
+### Peeling
+
+References stored in a reftable are peeled, a record for an annotated
+(or signed) tag records both the tag object, and the object it refers
+to.
+
+### Reference name encoding
+
+Reference names are an uninterpreted sequence of bytes that must pass
+[git-check-ref-format][ref-fmt] as a valid reference name.
+
+[ref-fmt]: https://git-scm.com/docs/git-check-ref-format
+
+### Network byte order
+
+All multi-byte, fixed width fields are in network byte order.
+
+### Ordering
+
+Blocks are lexicographically ordered by their first reference.
+
+### Directory/file conflicts
+
+The reftable format accepts both `refs/heads/foo` and
+`refs/heads/foo/bar` as distinct references.
+
+This property is useful for retaining log records in reftable, but may
+confuse versions of Git using `$GIT_DIR/refs` directory tree to
+maintain references. Users of reftable may choose to continue to
+reject `foo` and `foo/bar` type conflicts to prevent problems for
+peers.
+
+## File format
+
+### Structure
+
+A reftable file has the following high-level structure:
+
+ first_block {
+ header
+ first_ref_block
+ }
+ ref_block*
+ ref_index*
+ obj_block*
+ obj_index*
+ log_block*
+ log_index*
+ footer
+
+A log-only file omits the `ref_block`, `ref_index`, `obj_block` and
+`obj_index` sections, containing only the file header and log block:
+
+ first_block {
+ header
+ }
+ log_block*
+ log_index*
+ footer
+
+in a log-only file the first log block immediately follows the file
+header, without padding to block alignment.
+
+### Block size
+
+The file's block size is arbitrarily determined by the writer, and
+does not have to be a power of 2. The block size must be larger than
+the longest reference name or log entry used in the repository, as
+references cannot span blocks.
+
+Powers of two that are friendly to the virtual memory system or
+filesystem (such as 4k or 8k) are recommended. Larger sizes (64k) can
+yield better compression, with a possible increased cost incurred by
+readers during access.
+
+The largest block size is `16777215` bytes (15.99 MiB).
+
+### Block alignment
+
+Writers may choose to align blocks at multiples of the block size by
+including `padding` filled with NUL bytes at the end of a block to
+round out to the chosen alignment. When alignment is used, writers
+must specify the alignment with the file header's `block_size` field.
+
+Block alignment is not required by the file format. Unaligned files
+must set `block_size = 0` in the file header, and omit `padding`.
+Unaligned files with more than one ref block must include the
+[ref index](#Ref-index) to support fast lookup. Readers must be
+able to read both aligned and non-aligned files.
+
+Very small files (e.g. 1 only ref block) may omit `padding` and the
+ref index to reduce total file size.
+
+### Header
+
+A 24-byte header appears at the beginning of the file:
+
+ 'REFT'
+ uint8( version_number = 1 )
+ uint24( block_size )
+ uint64( min_update_index )
+ uint64( max_update_index )
+
+Aligned files must specify `block_size` to configure readers with the
+expected block alignment. Unaligned files must set `block_size = 0`.
+
+The `min_update_index` and `max_update_index` describe bounds for the
+`update_index` field of all log records in this file. When reftables
+are used in a stack for [transactions](#Update-transactions), these
+fields can order the files such that the prior file's
+`max_update_index + 1` is the next file's `min_update_index`.
+
+### First ref block
+
+The first ref block shares the same block as the file header, and is
+24 bytes smaller than all other blocks in the file. The first block
+immediately begins after the file header, at position 24.
+
+If the first block is a log block (a log-only file), its block header
+begins immediately at position 24.
+
+### Ref block format
+
+A ref block is written as:
+
+ 'r'
+ uint24( block_len )
+ ref_record+
+ uint24( restart_offset )+
+ uint16( restart_count )
+
+ padding?
+
+Blocks begin with `block_type = 'r'` and a 3-byte `block_len` which
+encodes the number of bytes in the block up to, but not including the
+optional `padding`. This is always less than or equal to the file's
+block size. In the first ref block, `block_len` includes 24 bytes
+for the file header.
+
+The 2-byte `restart_count` stores the number of entries in the
+`restart_offset` list, which must not be empty. Readers can use
+`restart_count` to binary search between restarts before starting a
+linear scan.
+
+Exactly `restart_count` 3-byte `restart_offset` values precedes the
+`restart_count`. Offsets are relative to the start of the block and
+refer to the first byte of any `ref_record` whose name has not been
+prefix compressed. Entries in the `restart_offset` list must be
+sorted, ascending. Readers can start linear scans from any of these
+records.
+
+A variable number of `ref_record` fill the middle of the block,
+describing reference names and values. The format is described below.
+
+As the first ref block shares the first file block with the file
+header, all `restart_offset` in the first block are relative to the
+start of the file (position 0), and include the file header. This
+forces the first `restart_offset` to be `28`.
+
+#### ref record
+
+A `ref_record` describes a single reference, storing both the name and
+its value(s). Records are formatted as:
+
+ varint( prefix_length )
+ varint( (suffix_length << 3) | value_type )
+ suffix
+ varint( update_index_delta )
+ value?
+
+The `prefix_length` field specifies how many leading bytes of the
+prior reference record's name should be copied to obtain this
+reference's name. This must be 0 for the first reference in any
+block, and also must be 0 for any `ref_record` whose offset is listed
+in the `restart_offset` table at the end of the block.
+
+Recovering a reference name from any `ref_record` is a simple concat:
+
+ this_name = prior_name[0..prefix_length] + suffix
+
+The `suffix_length` value provides the number of bytes available in
+`suffix` to copy from `suffix` to complete the reference name.
+
+The `update_index` that last modified the reference can be obtained by
+adding `update_index_delta` to the `min_update_index` from the file
+header: `min_update_index + update_index_delta`.
+
+The `value` follows. Its format is determined by `value_type`, one of
+the following:
+
+- `0x0`: deletion; no value data (see transactions, below)
+- `0x1`: one 20-byte object id; value of the ref
+- `0x2`: two 20-byte object ids; value of the ref, peeled target
+- `0x3`: symbolic reference: `varint( target_len ) target`
+
+Symbolic references use `0x3`, followed by the complete name of the
+reference target. No compression is applied to the target name.
+
+Types `0x4..0x7` are reserved for future use.
+
+### Ref index
+
+The ref index stores the name of the last reference from every ref
+block in the file, enabling reduced disk seeks for lookups. Any
+reference can be found by searching the index, identifying the
+containing block, and searching within that block.
+
+The index may be organized into a multi-level index, where the 1st
+level index block points to additional ref index blocks (2nd level),
+which may in turn point to either additional index blocks (e.g. 3rd
+level) or ref blocks (leaf level). Disk reads required to access a
+ref go up with higher index levels. Multi-level indexes may be
+required to ensure no single index block exceeds the file format's max
+block size of `16777215` bytes (15.99 MiB). To acheive constant O(1)
+disk seeks for lookups the index must be a single level, which is
+permitted to exceed the file's configured block size, but not the
+format's max block size of 15.99 MiB.
+
+If present, the ref index block(s) appears after the last ref block.
+
+If there are at least 4 ref blocks, a ref index block should be
+written to improve lookup times. Cold reads using the index require
+2 disk reads (read index, read block), and binary searching < 4 blocks
+also requires <= 2 reads. Omitting the index block from smaller files
+saves space.
+
+If the file is unaligned and contains more than one ref block, the ref
+index must be written.
+
+Index block format:
+
+ 'i'
+ uint24( block_len )
+ index_record+
+ uint24( restart_offset )+
+ uint16( restart_count )
+
+ padding?
+
+The index blocks begin with `block_type = 'i'` and a 3-byte
+`block_len` which encodes the number of bytes in the block,
+up to but not including the optional `padding`.
+
+The `restart_offset` and `restart_count` fields are identical in
+format, meaning and usage as in ref blocks.
+
+To reduce the number of reads required for random access in very large
+files the index block may be larger than other blocks. However,
+readers must hold the entire index in memory to benefit from this, so
+it's a time-space tradeoff in both file size and reader memory.
+
+Increasing the file's block size decreases the index size.
+Alternatively a multi-level index may be used, keeping index blocks
+within the file's block size, but increasing the number of blocks
+that need to be accessed.
+
+#### index record
+
+An index record describes the last entry in another block.
+Index records are written as:
+
+ varint( prefix_length )
+ varint( (suffix_length << 3) | 0 )
+ suffix
+ varint( block_position )
+
+Index records use prefix compression exactly like `ref_record`.
+
+Index records store `block_position` after the suffix, specifying the
+absolute position in bytes (from the start of the file) of the block
+that ends with this reference. Readers can seek to `block_position` to
+begin reading the block header.
+
+Readers must examine the block header at `block_position` to determine
+if the next block is another level index block, or the leaf-level ref
+block.
+
+#### Reading the index
+
+Readers loading the ref index must first read the footer (below) to
+obtain `ref_index_position`. If not present, the position will be 0.
+The `ref_index_position` is for the 1st level root of the ref index.
+
+### Obj block format
+
+Object blocks are optional. Writers may choose to omit object blocks,
+especially if readers will not use the SHA-1 to ref mapping.
+
+Object blocks use unique, abbreviated 2-20 byte SHA-1 keys, mapping
+to ref blocks containing references pointing to that object directly,
+or as the peeled value of an annotated tag. Like ref blocks, object
+blocks use the file's standard block size. The abbrevation length is
+available in the footer as `obj_id_len`.
+
+To save space in small files, object blocks may be omitted if the ref
+index is not present, as brute force search will only need to read a
+few ref blocks. When missing, readers should brute force a linear
+search of all references to lookup by SHA-1.
+
+An object block is written as:
+
+ 'o'
+ uint24( block_len )
+ obj_record+
+ uint24( restart_offset )+
+ uint16( restart_count )
+
+ padding?
+
+Fields are identical to ref block. Binary search using the restart
+table works the same as in reference blocks.
+
+Because object identifiers are abbreviated by writers to the shortest
+unique abbreviation within the reftable, obj key lengths are variable
+between 2 and 20 bytes. Readers must compare only for common prefix
+match within an obj block or obj index.
+
+#### obj record
+
+An `obj_record` describes a single object abbreviation, and the blocks
+containing references using that unique abbreviation:
+
+ varint( prefix_length )
+ varint( (suffix_length << 3) | cnt_3 )
+ suffix
+ varint( cnt_large )?
+ varint( position_delta )*
+
+Like in reference blocks, abbreviations are prefix compressed within
+an obj block. On large reftables with many unique objects, higher
+block sizes (64k), and higher restart interval (128), a
+`prefix_length` of 2 or 3 and `suffix_length` of 3 may be common in
+obj records (unique abbreviation of 5-6 raw bytes, 10-12 hex digits).
+
+Each record contains `position_count` number of positions for matching
+ref blocks. For 1-7 positions the count is stored in `cnt_3`. When
+`cnt_3 = 0` the actual count follows in a varint, `cnt_large`.
+
+The use of `cnt_3` bets most objects are pointed to by only a single
+reference, some may be pointed to by a couple of references, and very
+few (if any) are pointed to by more than 7 references.
+
+A special case exists when `cnt_3 = 0` and `cnt_large = 0`: there
+are no `position_delta`, but at least one reference starts with this
+abbreviation. A reader that needs exact reference names must scan all
+references to find which specific references have the desired object.
+Writers should use this format when the `position_delta` list would have
+overflowed the file's block size due to a high number of references
+pointing to the same object.
+
+The first `position_delta` is the position from the start of the file.
+Additional `position_delta` entries are sorted ascending and relative
+to the prior entry, e.g. a reader would perform:
+
+ pos = position_delta[0]
+ prior = pos
+ for (j = 1; j < position_count; j++) {
+ pos = prior + position_delta[j]
+ prior = pos
+ }
+
+With a position in hand, a reader must linearly scan the ref block,
+starting from the first `ref_record`, testing each reference's SHA-1s
+(for `value_type = 0x1` or `0x2`) for full equality. Faster searching
+by SHA-1 within a single ref block is not supported by the reftable
+format. Smaller block sizes reduce the number of candidates this step
+must consider.
+
+### Obj index
+
+The obj index stores the abbreviation from the last entry for every
+obj block in the file, enabling reduced disk seeks for all lookups.
+It is formatted exactly the same as the ref index, but refers to obj
+blocks.
+
+The obj index should be present if obj blocks are present, as
+obj blocks should only be written in larger files.
+
+Readers loading the obj index must first read the footer (below) to
+obtain `obj_index_position`. If not present, the position will be 0.
+
+### Log block format
+
+Unlike ref and obj blocks, log blocks are always unaligned.
+
+Log blocks are variable in size, and do not match the `block_size`
+specified in the file header or footer. Writers should choose an
+appropriate buffer size to prepare a log block for deflation, such as
+`2 * block_size`.
+
+A log block is written as:
+
+ 'g'
+ uint24( block_len )
+ zlib_deflate {
+ log_record+
+ uint24( restart_offset )+
+ uint16( restart_count )
+ }
+
+Log blocks look similar to ref blocks, except `block_type = 'g'`.
+
+The 4-byte block header is followed by the deflated block contents
+using zlib deflate. The `block_len` in the header is the inflated
+size (including 4-byte block header), and should be used by readers to
+preallocate the inflation output buffer. A log block's `block_len`
+may exceed the file's block size.
+
+Offsets within the log block (e.g. `restart_offset`) still include
+the 4-byte header. Readers may prefer prefixing the inflation output
+buffer with the 4-byte header.
+
+Within the deflate container, a variable number of `log_record`
+describe reference changes. The log record format is described
+below. See ref block format (above) for a description of
+`restart_offset` and `restart_count`.
+
+Because log blocks have no alignment or padding between blocks,
+readers must keep track of the bytes consumed by the inflater to
+know where the next log block begins.
+
+#### log record
+
+Log record keys are structured as:
+
+ ref_name '\0' reverse_int64( update_index )
+
+where `update_index` is the unique transaction identifier. The
+`update_index` field must be unique within the scope of a `ref_name`.
+See the update transactions section below for further details.
+
+The `reverse_int64` function inverses the value so lexographical
+ordering the network byte order encoding sorts the more recent records
+with higher `update_index` values first:
+
+ reverse_int64(int64 t) {
+ return 0xffffffffffffffff - t;
+ }
+
+Log records have a similar starting structure to ref and index
+records, utilizing the same prefix compression scheme applied to the
+log record key described above.
+
+```
+ varint( prefix_length )
+ varint( (suffix_length << 3) | log_type )
+ suffix
+ log_data {
+ old_id
+ new_id
+ varint( name_length ) name
+ varint( email_length ) email
+ varint( time_seconds )
+ sint16( tz_offset )
+ varint( message_length ) message
+ }?
+```
+
+Log record entries use `log_type` to indicate what follows:
+
+- `0x0`: deletion; no log data.
+- `0x1`: standard git reflog data using `log_data` above.
+
+The `log_type = 0x0` is mostly useful for `git stash drop`, removing
+an entry from the reflog of `refs/stash` in a transaction file
+(below), without needing to rewrite larger files. Readers reading a
+stack of reflogs must treat this as a deletion.
+
+For `log_type = 0x1`, the `log_data` section follows
+[git update-ref][update-ref] logging, and includes:
+
+- two 20-byte SHA-1s (old id, new id)
+- varint string of committer's name
+- varint string of committer's email
+- varint time in seconds since epoch (Jan 1, 1970)
+- 2-byte timezone offset in minutes (signed)
+- varint string of message
+
+`tz_offset` is the absolute number of minutes from GMT the committer
+was at the time of the update. For example `GMT-0800` is encoded in
+reftable as `sint16(-480)` and `GMT+0230` is `sint16(150)`.
+
+The committer email does not contain `<` or `>`, it's the value
+normally found between the `<>` in a git commit object header.
+
+The `message_length` may be 0, in which case there was no message
+supplied for the update.
+
+[update-ref]: https://git-scm.com/docs/git-update-ref#_logging_updates
+
+#### Reading the log
+
+Readers accessing the log must first read the footer (below) to
+determine the `log_position`. The first block of the log begins at
+`log_position` bytes since the start of the file. The `log_position`
+is not block aligned.
+
+#### Importing logs
+
+When importing from `$GIT_DIR/logs` writers should globally order all
+log records roughly by timestamp while preserving file order, and
+assign unique, increasing `update_index` values for each log line.
+Newer log records get higher `update_index` values.
+
+Although an import may write only a single reftable file, the reftable
+file must span many unique `update_index`, as each log line requires
+its own `update_index` to preserve semantics.
+
+### Log index
+
+The log index stores the log key (`refname \0 reverse_int64(update_index)`)
+for the last log record of every log block in the file, supporting
+bounded-time lookup.
+
+A log index block must be written if 2 or more log blocks are written
+to the file. If present, the log index appears after the last log
+block. There is no padding used to align the log index to block
+alignment.
+
+Log index format is identical to ref index, except the keys are 9
+bytes longer to include `'\0'` and the 8-byte
+`reverse_int64(update_index)`. Records use `block_position` to
+refer to the start of a log block.
+
+#### Reading the index
+
+Readers loading the log index must first read the footer (below) to
+obtain `log_index_position`. If not present, the position will be 0.
+
+### Footer
+
+After the last block of the file, a file footer is written. It begins
+like the file header, but is extended with additional data.
+
+A 68-byte footer appears at the end:
+
+```
+ 'REFT'
+ uint8( version_number = 1 )
+ uint24( block_size )
+ uint64( min_update_index )
+ uint64( max_update_index )
+
+ uint64( ref_index_position )
+ uint64( (obj_position << 5) | obj_id_len )
+ uint64( obj_index_position )
+
+ uint64( log_position )
+ uint64( log_index_position )
+
+ uint32( CRC-32 of above )
+```
+
+If a section is missing (e.g. ref index) the corresponding position
+field (e.g. `ref_index_position`) will be 0.
+
+- `obj_position`: byte position for the first obj block.
+- `obj_id_len`: number of bytes used to abbreviate object identifiers
+ in obj blocks.
+- `log_position`: byte position for the first log block.
+- `ref_index_position`: byte position for the start of the ref index.
+- `obj_index_position`: byte position for the start of the obj index.
+- `log_index_position`: byte position for the start of the log index.
+
+#### Reading the footer
+
+Readers must seek to `file_length - 68` to access the footer. A
+trusted external source (such as `stat(2)`) is necessary to obtain
+`file_length`. When reading the footer, readers must verify:
+
+- 4-byte magic is correct
+- 1-byte version number is recognized
+- 4-byte CRC-32 matches the other 64 bytes (including magic, and version)
+
+Once verified, the other fields of the footer can be accessed.
+
+### Varint encoding
+
+Varint encoding is identical to the ofs-delta encoding method used
+within pack files.
+
+Decoder works such as:
+
+ val = buf[ptr] & 0x7f
+ while (buf[ptr] & 0x80) {
+ ptr++
+ val = ((val + 1) << 7) | (buf[ptr] & 0x7f)
+ }
+
+### Binary search
+
+Binary search within a block is supported by the `restart_offset`
+fields at the end of the block. Readers can binary search through the
+restart table to locate between which two restart points the sought
+reference or key should appear.
+
+Each record identified by a `restart_offset` stores the complete key
+in the `suffix` field of the record, making the compare operation
+during binary search straightforward.
+
+Once a restart point lexicographically before the sought reference has
+been identified, readers can linearly scan through the following
+record entries to locate the sought record, terminating if the current
+record sorts after (and therefore the sought key is not present).
+
+#### Restart point selection
+
+Writers determine the restart points at file creation. The process is
+arbitrary, but every 16 or 64 records is recommended. Every 16 may
+be more suitable for smaller block sizes (4k or 8k), every 64 for
+larger block sizes (64k).
+
+More frequent restart points reduces prefix compression and increases
+space consumed by the restart table, both of which increase file size.
+
+Less frequent restart points makes prefix compression more effective,
+decreasing overall file size, with increased penalities for readers
+walking through more records after the binary search step.
+
+A maximum of `65535` restart points per block is supported.
+
+## Considerations
+
+### Lightweight refs dominate
+
+The reftable format assumes the vast majority of references are single
+SHA-1 valued with common prefixes, such as Gerrit Code Review's
+`refs/changes/` namespace, GitHub's `refs/pulls/` namespace, or many
+lightweight tags in the `refs/tags/` namespace.
+
+Annotated tags storing the peeled object cost an additional 20 bytes
+per reference.
+
+### Low overhead
+
+A reftable with very few references (e.g. git.git with 5 heads)
+is 269 bytes for reftable, vs. 332 bytes for packed-refs. This
+supports reftable scaling down for transaction logs (below).
+
+### Block size
+
+For a Gerrit Code Review type repository with many change refs, larger
+block sizes (64 KiB) and less frequent restart points (every 64) yield
+better compression due to more references within the block compressing
+against the prior reference.
+
+Larger block sizes reduce the index size, as the reftable will
+require fewer blocks to store the same number of references.
+
+### Minimal disk seeks
+
+Assuming the index block has been loaded into memory, binary searching
+for any single reference requires exactly 1 disk seek to load the
+containing block.
+
+### Scans and lookups dominate
+
+Scanning all references and lookup by name (or namespace such as
+`refs/heads/`) are the most common activities performed on repositories.
+SHA-1s are stored directly with references to optimize this use case.
+
+### Logs are infrequently read
+
+Logs are infrequently accessed, but can be large. Deflating log
+blocks saves disk space, with some increased penalty at read time.
+
+Logs are stored in an isolated section from refs, reducing the burden
+on reference readers that want to ignore logs. Further, historical
+logs can be isolated into log-only files.
+
+### Logs are read backwards
+
+Logs are frequently accessed backwards (most recent N records for
+master to answer `master@{4}`), so log records are grouped by
+reference, and sorted descending by update index.
+
+## Repository format
+
+### Version 1
+
+A repository must set its `$GIT_DIR/config` to configure reftable:
+
+ [core]
+ repositoryformatversion = 1
+ [extensions]
+ refStorage = reftable
+
+### Layout
+
+The `$GIT_DIR/refs` path is a file when reftable is configured, not a
+directory. This prevents loose references from being stored.
+
+A collection of reftable files are stored in the `$GIT_DIR/reftable/`
+directory:
+
+ 00000001.log
+ 00000001.ref
+ 00000002.ref
+
+where reftable files are named by a unique name such as produced by
+the function `${update_index}.ref`.
+
+Log-only files use the `.log` extension, while ref-only and mixed ref
+and log files use `.ref`. extension.
+
+The stack ordering file is `$GIT_DIR/refs` and lists the current
+files, one per line, in order, from oldest (base) to newest (most
+recent):
+
+ $ cat .git/refs
+ 00000001.log
+ 00000001.ref
+ 00000002.ref
+
+Readers must read `$GIT_DIR/refs` to determine which files are
+relevant right now, and search through the stack in reverse order
+(last reftable is examined first).
+
+Reftable files not listed in `refs` may be new (and about to be added
+to the stack by the active writer), or ancient and ready to be pruned.
+
+### Readers
+
+Readers can obtain a consistent snapshot of the reference space by
+following:
+
+1. Open and read the `refs` file.
+2. Open each of the reftable files that it mentions.
+3. If any of the files is missing, goto 1.
+4. Read from the now-open files as long as necessary.
+
+### Update transactions
+
+Although reftables are immutable, mutations are supported by writing a
+new reftable and atomically appending it to the stack:
+
+1. Acquire `refs.lock`.
+2. Read `refs` to determine current reftables.
+3. Select `update_index` to be most recent file's `max_update_index + 1`.
+4. Prepare temp reftable `${update_index}_XXXXXX`, including log entries.
+5. Rename `${update_index}_XXXXXX` to `${update_index}.ref`.
+6. Copy `refs` to `refs.lock`, appending file from (5).
+7. Rename `refs.lock` to `refs`.
+
+During step 4 the new file's `min_update_index` and `max_update_index`
+are both set to the `update_index` selected by step 3. All log
+records for the transaction use the same `update_index` in their keys.
+This enables later correlation of which references were updated by the
+same transaction.
+
+Because a single `refs.lock` file is used to manage locking, the
+repository is single-threaded for writers. Writers may have to
+busy-spin (with backoff) around creating `refs.lock`, for up to an
+acceptable wait period, aborting if the repository is too busy to
+mutate. Application servers wrapped around repositories (e.g. Gerrit
+Code Review) can layer their own lock/wait queue to improve fairness
+to writers.
+
+### Reference deletions
+
+Deletion of any reference can be explicitly stored by setting the
+`type` to `0x0` and omitting the `value` field of the `ref_record`.
+This serves as a tombstone, overriding any assertions about the
+existence of the reference from earlier files in the stack.
+
+### Compaction
+
+A partial stack of reftables can be compacted by merging references
+using a straightforward merge join across reftables, selecting the
+most recent value for output, and omitting deleted references that do
+not appear in remaining, lower reftables.
+
+A compacted reftable should set its `min_update_index` to the smallest of
+the input files' `min_update_index`, and its `max_update_index`
+likewise to the largest input `max_update_index`.
+
+For sake of illustration, assume the stack currently consists of
+reftable files (from oldest to newest): A, B, C, and D. The compactor
+is going to compact B and C, leaving A and D alone.
+
+1. Obtain lock `refs.lock` and read the `refs` file.
+2. Obtain locks `B.lock` and `C.lock`.
+ Ownership of these locks prevents other processes from trying
+ to compact these files.
+3. Release `refs.lock`.
+4. Compact `B` and `C` into a temp file `${min_update_index}_XXXXXX`.
+5. Reacquire lock `refs.lock`.
+6. Verify that `B` and `C` are still in the stack, in that order. This
+ should always be the case, assuming that other processes are adhering
+ to the locking protocol.
+7. Rename `${min_update_index}_XXXXXX` to `${min_update_index}_2.ref`.
+8. Write the new stack to `refs.lock`, replacing `B` and `C` with the
+ file from (4).
+9. Rename `refs.lock` to `refs`.
+10. Delete `B` and `C`, perhaps after a short sleep to avoid forcing
+ readers to backtrack.
+
+This strategy permits compactions to proceed independently of updates.
+
+## Alternatives considered
+
+### bzip packed-refs
+
+`bzip2` can significantly shrink a large packed-refs file (e.g. 62
+MiB compresses to 23 MiB, 37%). However the bzip format does not support
+random access to a single reference. Readers must inflate and discard
+while performing a linear scan.
+
+Breaking packed-refs into chunks (individually compressing each chunk)
+would reduce the amount of data a reader must inflate, but still
+leaves the problem of indexing chunks to support readers efficiently
+locating the correct chunk.
+
+Given the compression achieved by reftable's encoding, it does not
+seem necessary to add the complexity of bzip/gzip/zlib.
+
+### Michael Haggerty's alternate format
+
+Michael Haggerty proposed [an alternate][mh-alt] format to reftable on
+the Git mailing list. This format uses smaller chunks, without the
+restart table, and avoids block alignment with padding. Reflog entries
+immediately follow each ref, and are thus interleaved between refs.
+
+Performance testing indicates reftable is faster for lookups (51%
+faster, 11.2 usec vs. 5.4 usec), although reftable produces a
+slightly larger file (+ ~3.2%, 28.3M vs 29.2M):
+
+format | size | seek cold | seek hot |
+---------:|-------:|----------:|----------:|
+mh-alt | 28.3 M | 23.4 usec | 11.2 usec |
+reftable | 29.2 M | 19.9 usec | 5.4 usec |
+
+[mh-alt]: https://public-inbox.org/git/CAMy9T_HCnyc1g8XWOOWhe7nN0aEFyyBskV2aOMb_fe+wGvEJ7A@mail.gmail.com/
+
+### JGit Ketch RefTree
+
+[JGit Ketch][ketch] proposed [RefTree][reftree], an encoding of
+references inside Git tree objects stored as part of the repository's
+object database.
+
+The RefTree format adds additional load on the object database storage
+layer (more loose objects, more objects in packs), and relies heavily
+on the packer's delta compression to save space. Namespaces which are
+flat (e.g. thousands of tags in refs/tags) initially create very
+large loose objects, and so RefTree does not address the problem of
+copying many references to modify a handful.
+
+Flat namespaces are not efficiently searchable in RefTree, as tree
+objects in canonical formatting cannot be binary searched. This fails
+the need to handle a large number of references in a single namespace,
+such as GitHub's `refs/pulls`, or a project with many tags.
+
+[ketch]: https://dev.eclipse.org/mhonarc/lists/jgit-dev/msg03073.html
+[reftree]: https://public-inbox.org/git/CAJo=hJvnAPNAdDcAAwAvU9C4RVeQdoS3Ev9WTguHx4fD0V_nOg@mail.gmail.com/
+
+### LMDB
+
+David Turner proposed [using LMDB][dt-lmdb], as LMDB is lightweight
+(64k of runtime code) and GPL-compatible license.
+
+A downside of LMDB is its reliance on a single C implementation. This
+makes embedding inside JGit (a popular reimplemenation of Git)
+difficult, and hoisting onto virtual storage (for JGit DFS) virtually
+impossible.
+
+A common format that can be supported by all major Git implementations
+(git-core, JGit, libgit2) is strongly preferred.
+
+[dt-lmdb]: https://public-inbox.org/git/1455772670-21142-26-git-send-email-dturner@twopensource.com/
+
+## Future
+
+### Longer hashes
+
+Version will bump (e.g. 2) to indicate `value` uses a different
+object id length other than 20. The length could be stored in an
+expanded file header, or hardcoded as part of the version.
diff --git a/WORKSPACE b/WORKSPACE
index f2fecb5..afd58b2 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -77,8 +77,8 @@
maven_jar(
name = "args4j",
- artifact = "args4j:args4j:2.0.15",
- sha1 = "139441471327b9cc6d56436cb2a31e60eb6ed2ba",
+ artifact = "args4j:args4j:2.33",
+ sha1 = "bd87a75374a6d6523de82fef51fc3cfe9baf9fc9",
)
maven_jar(
diff --git a/lib/BUILD b/lib/BUILD
index 346e1fd..a3936ee 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -106,7 +106,10 @@
java_library(
name = "jsch",
- visibility = ["//org.eclipse.jgit:__pkg__"],
+ visibility = [
+ "//org.eclipse.jgit:__pkg__",
+ "//org.eclipse.jgit.test:__pkg__",
+ ],
exports = ["@jsch//jar"],
)
diff --git a/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.ant.test/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.ant.test/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.ant.test/.settings/org.eclipse.pde.api.tools.prefs
index fb04636..c0030de 100644
--- a/org.eclipse.jgit.ant.test/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.ant.test/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,4 +1,4 @@
-#Tue Oct 18 02:04:38 CEST 2011
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -8,6 +8,10 @@
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -47,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -58,6 +63,7 @@
INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
INVALID_JAVADOC_TAG=Ignore
INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
LEAK_EXTEND=Warning
@@ -75,6 +81,7 @@
METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
@@ -83,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
index 5889109..78c6392 100644
--- a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
@@ -3,13 +3,13 @@
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit.ant.test
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: org.apache.tools.ant,
- org.eclipse.jgit.ant.tasks;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.junit;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.ant.tasks;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.junit;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util;version="[4.9.9,4.10.0)",
org.hamcrest.core;version="[1.1.0,2.0.0)",
org.junit;version="[4.0.0,5.0.0)"
diff --git a/org.eclipse.jgit.ant.test/pom.xml b/org.eclipse.jgit.ant.test/pom.xml
index 386ecd2..2ea73d8 100644
--- a/org.eclipse.jgit.ant.test/pom.xml
+++ b/org.eclipse.jgit.ant.test/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.ant.test</artifactId>
diff --git a/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.ant/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.ant/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.ant/.settings/org.eclipse.pde.api.tools.prefs
index cd148d9..c0030de 100644
--- a/org.eclipse.jgit.ant/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.ant/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,4 +1,4 @@
-#Tue Oct 18 00:52:01 CEST 2011
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -8,6 +8,10 @@
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -47,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -58,6 +63,7 @@
INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
INVALID_JAVADOC_TAG=Ignore
INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
LEAK_EXTEND=Warning
@@ -75,6 +81,7 @@
METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
@@ -83,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
index be009d1..e4657f8 100644
--- a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
@@ -2,11 +2,11 @@
Bundle-ManifestVersion: 2
Bundle-Name: %Bundle-Name
Bundle-SymbolicName: org.eclipse.jgit.ant
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: org.apache.tools.ant,
- org.eclipse.jgit.storage.file;version="[4.8.1,4.9.0)"
+ org.eclipse.jgit.storage.file;version="[4.9.9,4.10.0)"
Bundle-Localization: plugin
Bundle-Vendor: %Provider-Name
-Export-Package: org.eclipse.jgit.ant.tasks;version="4.8.1";
+Export-Package: org.eclipse.jgit.ant.tasks;version="4.9.9";
uses:="org.apache.tools.ant.types,org.apache.tools.ant"
diff --git a/org.eclipse.jgit.ant/pom.xml b/org.eclipse.jgit.ant/pom.xml
index 5712d29..41be91d 100644
--- a/org.eclipse.jgit.ant/pom.xml
+++ b/org.eclipse.jgit.ant/pom.xml
@@ -48,7 +48,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.ant</artifactId>
diff --git a/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.archive/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.archive/.settings/org.eclipse.pde.api.tools.prefs
index cd148d9..c0030de 100644
--- a/org.eclipse.jgit.archive/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.archive/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,4 +1,4 @@
-#Tue Oct 18 00:52:01 CEST 2011
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -8,6 +8,10 @@
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -47,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -58,6 +63,7 @@
INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
INVALID_JAVADOC_TAG=Ignore
INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
LEAK_EXTEND=Warning
@@ -75,6 +81,7 @@
METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
@@ -83,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
index 4080946..88e1c20 100644
--- a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit.archive
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-Vendor: %provider_name
Bundle-Localization: plugin
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -12,15 +12,15 @@
org.apache.commons.compress.compressors.bzip2;version="[1.4,2.0)",
org.apache.commons.compress.compressors.gzip;version="[1.4,2.0)",
org.apache.commons.compress.compressors.xz;version="[1.4,2.0)",
- org.eclipse.jgit.api;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.api;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.nls;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.revwalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util;version="[4.9.9,4.10.0)",
org.osgi.framework;version="[1.3.0,2.0.0)"
Bundle-ActivationPolicy: lazy
Bundle-Activator: org.eclipse.jgit.archive.FormatActivator
-Export-Package: org.eclipse.jgit.archive;version="4.8.1";
+Export-Package: org.eclipse.jgit.archive;version="4.9.9";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.api,
org.apache.commons.compress.archivers,
diff --git a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
index c03191a..4aec44f 100644
--- a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
Bundle-Name: org.eclipse.jgit.archive - Sources
Bundle-SymbolicName: org.eclipse.jgit.archive.source
Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 4.8.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.8.1.qualifier";roots="."
+Bundle-Version: 4.9.9.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.9.9.qualifier";roots="."
diff --git a/org.eclipse.jgit.archive/pom.xml b/org.eclipse.jgit.archive/pom.xml
index eea1d80..603ee84 100644
--- a/org.eclipse.jgit.archive/pom.xml
+++ b/org.eclipse.jgit.archive/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.archive</artifactId>
diff --git a/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.http.apache/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.http.apache/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.http.apache/.settings/org.eclipse.pde.api.tools.prefs
index cd148d9..c0030de 100644
--- a/org.eclipse.jgit.http.apache/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.http.apache/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,4 +1,4 @@
-#Tue Oct 18 00:52:01 CEST 2011
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -8,6 +8,10 @@
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -47,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -58,6 +63,7 @@
INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
INVALID_JAVADOC_TAG=Ignore
INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
LEAK_EXTEND=Warning
@@ -75,6 +81,7 @@
METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
@@ -83,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
index 2fc4543..0bec339 100644
--- a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
Bundle-ManifestVersion: 2
Bundle-Name: %Bundle-Name
Bundle-SymbolicName: org.eclipse.jgit.http.apache
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-Localization: plugin
Bundle-Vendor: %Provider-Name
@@ -22,10 +22,10 @@
org.apache.http.impl.client;version="[4.3.0,5.0.0)",
org.apache.http.impl.conn;version="[4.3.0,5.0.0)",
org.apache.http.params;version="[4.3.0,5.0.0)",
- org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport.http;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util;version="[4.8.1,4.9.0)"
-Export-Package: org.eclipse.jgit.transport.http.apache;version="4.8.1";
+ org.eclipse.jgit.nls;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport.http;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util;version="[4.9.9,4.10.0)"
+Export-Package: org.eclipse.jgit.transport.http.apache;version="4.9.9";
uses:="org.apache.http.client,
org.eclipse.jgit.transport.http,
org.apache.http.entity,
diff --git a/org.eclipse.jgit.http.apache/pom.xml b/org.eclipse.jgit.http.apache/pom.xml
index 068e367..0e21796 100644
--- a/org.eclipse.jgit.http.apache/pom.xml
+++ b/org.eclipse.jgit.http.apache/pom.xml
@@ -48,7 +48,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.http.apache</artifactId>
diff --git a/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.http.server/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.http.server/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.http.server/.settings/org.eclipse.pde.api.tools.prefs
index cd148d9..c0030de 100644
--- a/org.eclipse.jgit.http.server/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.http.server/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,4 +1,4 @@
-#Tue Oct 18 00:52:01 CEST 2011
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -8,6 +8,10 @@
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -47,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -58,6 +63,7 @@
INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
INVALID_JAVADOC_TAG=Ignore
INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
LEAK_EXTEND=Warning
@@ -75,6 +81,7 @@
METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
@@ -83,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
index 2b51b4b..444c666 100644
--- a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
@@ -2,13 +2,13 @@
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit.http.server
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-Localization: plugin
Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.http.server;version="4.8.1",
- org.eclipse.jgit.http.server.glue;version="4.8.1";
+Export-Package: org.eclipse.jgit.http.server;version="4.9.9",
+ org.eclipse.jgit.http.server.glue;version="4.9.9";
uses:="javax.servlet,javax.servlet.http",
- org.eclipse.jgit.http.server.resolver;version="4.8.1";
+ org.eclipse.jgit.http.server.resolver;version="4.9.9";
uses:="org.eclipse.jgit.transport.resolver,
org.eclipse.jgit.lib,
org.eclipse.jgit.transport,
@@ -17,12 +17,12 @@
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: javax.servlet;version="[2.5.0,3.2.0)",
javax.servlet.http;version="[2.5.0,3.2.0)",
- org.eclipse.jgit.errors;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport.resolver;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util;version="[4.8.1,4.9.0)"
+ org.eclipse.jgit.errors;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.nls;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.revwalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util;version="[4.9.9,4.10.0)"
diff --git a/org.eclipse.jgit.http.server/pom.xml b/org.eclipse.jgit.http.server/pom.xml
index a011594..4ef75de 100644
--- a/org.eclipse.jgit.http.server/pom.xml
+++ b/org.eclipse.jgit.http.server/pom.xml
@@ -52,7 +52,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.http.server</artifactId>
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java
index 03c9d8d..cfe4822 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitSmartHttpTools.java
@@ -201,7 +201,7 @@ public static void sendError(HttpServletRequest req,
} else {
if (httpStatus < 400)
ServletUtils.consumeRequestBody(req);
- res.sendError(httpStatus);
+ res.sendError(httpStatus, textForGit);
}
}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
index 1336d6e..c7fbaf6 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
@@ -112,7 +112,7 @@ public static InputStream getInputStream(final HttpServletRequest req)
throws IOException {
InputStream in = req.getInputStream();
final String enc = req.getHeader(HDR_CONTENT_ENCODING);
- if (ENCODING_GZIP.equals(enc) || ENCODING_X_GZIP.equals(enc)) //$NON-NLS-1$
+ if (ENCODING_GZIP.equals(enc) || ENCODING_X_GZIP.equals(enc))
in = new GZIPInputStream(in);
else if (enc != null)
throw new IOException(MessageFormat.format(HttpServerText.get().encodingNotSupportedByThisLibrary
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java
index 9c3ed50..47443f5 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java
@@ -60,4 +60,4 @@ public interface ServletBinder {
* the servlet to execute on this path.
*/
public void with(HttpServlet servlet);
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java
index 88ad472..d20fe9f 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java
@@ -47,7 +47,6 @@
import org.eclipse.jgit.http.server.GitServlet;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
@@ -71,13 +70,6 @@ public void access(HttpServletRequest req, Repository db)
}
};
- private static final SectionParser<ServiceConfig> CONFIG = new SectionParser<ServiceConfig>() {
- @Override
- public ServiceConfig parse(final Config cfg) {
- return new ServiceConfig(cfg);
- }
- };
-
private static class ServiceConfig {
final boolean enabled;
@@ -96,7 +88,7 @@ private static class ServiceConfig {
* {@code true}.
*/
protected static boolean isEnabled(Repository db) {
- return db.getConfig().get(CONFIG).enabled;
+ return db.getConfig().get(ServiceConfig::new).enabled;
}
/**
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultReceivePackFactory.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultReceivePackFactory.java
index 04e192b..c0ffbb6 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultReceivePackFactory.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultReceivePackFactory.java
@@ -46,7 +46,6 @@
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.ReceivePack;
@@ -68,13 +67,6 @@
*/
public class DefaultReceivePackFactory implements
ReceivePackFactory<HttpServletRequest> {
- private static final SectionParser<ServiceConfig> CONFIG = new SectionParser<ServiceConfig>() {
- @Override
- public ServiceConfig parse(final Config cfg) {
- return new ServiceConfig(cfg);
- }
- };
-
private static class ServiceConfig {
final boolean set;
@@ -89,7 +81,7 @@ private static class ServiceConfig {
@Override
public ReceivePack create(final HttpServletRequest req, final Repository db)
throws ServiceNotEnabledException, ServiceNotAuthorizedException {
- final ServiceConfig cfg = db.getConfig().get(CONFIG);
+ final ServiceConfig cfg = db.getConfig().get(ServiceConfig::new);
String user = req.getRemoteUser();
if (cfg.set) {
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultUploadPackFactory.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultUploadPackFactory.java
index d01e2ef..642623b 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultUploadPackFactory.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/DefaultUploadPackFactory.java
@@ -46,7 +46,6 @@
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.UploadPack;
import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
@@ -61,13 +60,6 @@
*/
public class DefaultUploadPackFactory implements
UploadPackFactory<HttpServletRequest> {
- private static final SectionParser<ServiceConfig> CONFIG = new SectionParser<ServiceConfig>() {
- @Override
- public ServiceConfig parse(final Config cfg) {
- return new ServiceConfig(cfg);
- }
- };
-
private static class ServiceConfig {
final boolean enabled;
@@ -79,7 +71,7 @@ private static class ServiceConfig {
@Override
public UploadPack create(final HttpServletRequest req, final Repository db)
throws ServiceNotEnabledException, ServiceNotAuthorizedException {
- if (db.getConfig().get(CONFIG).enabled)
+ if (db.getConfig().get(ServiceConfig::new).enabled)
return new UploadPack(db);
else
throw new ServiceNotEnabledException();
diff --git a/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.http.test/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.http.test/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.http.test/.settings/org.eclipse.pde.api.tools.prefs
index cd148d9..c0030de 100644
--- a/org.eclipse.jgit.http.test/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.http.test/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,4 +1,4 @@
-#Tue Oct 18 00:52:01 CEST 2011
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -8,6 +8,10 @@
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -47,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -58,6 +63,7 @@
INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
INVALID_JAVADOC_TAG=Ignore
INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
LEAK_EXTEND=Warning
@@ -75,6 +81,7 @@
METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
@@ -83,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.http.test/BUILD b/org.eclipse.jgit.http.test/BUILD
index ce2d611..85a2242 100644
--- a/org.eclipse.jgit.http.test/BUILD
+++ b/org.eclipse.jgit.http.test/BUILD
@@ -34,6 +34,7 @@
srcs = glob(["src/**/*.java"]),
deps = [
"//lib:junit",
+ "//lib:servlet-api",
"//org.eclipse.jgit.http.server:jgit-servlet",
"//org.eclipse.jgit:jgit",
"//org.eclipse.jgit.junit.http:junit-http",
diff --git a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
index 811585e..33218fe 100644
--- a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
@@ -2,12 +2,14 @@
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit.http.test
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-Vendor: %provider_name
Bundle-Localization: plugin
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: javax.servlet;version="[2.5.0,3.2.0)",
javax.servlet.http;version="[2.5.0,3.2.0)",
+ org.apache.commons.codec;version="[1.6.0,2.0.0)",
+ org.apache.commons.codec.binary;version="[1.6.0,2.0.0)",
org.eclipse.jetty.continuation;version="[9.4.5,10.0.0)",
org.eclipse.jetty.http;version="[9.4.5,10.0.0)",
org.eclipse.jetty.io;version="[9.4.5,10.0.0)",
@@ -22,25 +24,27 @@
org.eclipse.jetty.util.log;version="[9.4.5,10.0.0)",
org.eclipse.jetty.util.security;version="[9.4.5,10.0.0)",
org.eclipse.jetty.util.thread;version="[9.4.5,10.0.0)",
- org.eclipse.jgit.errors;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.http.server;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.http.server.glue;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.http.server.resolver;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.junit;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.junit.http;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.storage.file;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport.http;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport.http.apache;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport.resolver;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.errors;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.http.server;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.http.server.glue;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.http.server.resolver;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.junit;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.junit.http;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.nls;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.revwalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.storage.file;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport.http;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport.http.apache;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util;version="[4.9.9,4.10.0)",
org.hamcrest.core;version="[1.1.0,2.0.0)",
org.junit;version="[4.0.0,5.0.0)",
+ org.junit.rules;version="[4.0.0,5.0.0)",
org.junit.runner;version="[4.0.0,5.0.0)",
org.junit.runners;version="[4.0.0,5.0.0)"
+Require-Bundle: org.hamcrest.library;bundle-version="[1.1.0,2.0.0)"
diff --git a/org.eclipse.jgit.http.test/pom.xml b/org.eclipse.jgit.http.test/pom.xml
index 1538601..838b996 100644
--- a/org.eclipse.jgit.http.test/pom.xml
+++ b/org.eclipse.jgit.http.test/pom.xml
@@ -51,7 +51,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.http.test</artifactId>
@@ -71,6 +71,13 @@
</dependency>
<dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ <scope>test</scope>
+ <version>[1.1.0,2.0.0)</version>
+ </dependency>
+
+ <dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>${project.version}</version>
@@ -87,14 +94,12 @@
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.junit.http</artifactId>
<version>${project.version}</version>
- <scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.junit</artifactId>
<version>${project.version}</version>
- <scope>test</scope>
</dependency>
<dependency>
@@ -107,7 +112,6 @@
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
- <scope>test</scope>
</dependency>
</dependencies>
diff --git a/org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/TestRepositoryResolver.java b/org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/TestRepositoryResolver.java
new file mode 100644
index 0000000..334e57c
--- /dev/null
+++ b/org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/TestRepositoryResolver.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016, 2017 Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.http.test;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.resolver.RepositoryResolver;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+
+/** A simple repository resolver for tests. */
+public final class TestRepositoryResolver
+ implements RepositoryResolver<HttpServletRequest> {
+
+ private final TestRepository<Repository> repo;
+
+ private final String repoName;
+
+ /**
+ * Creates a new {@link TestRepositoryResolver} that resolves the given name to
+ * the given repository.
+ *
+ * @param repo
+ * to resolve to
+ * @param repoName
+ * to match
+ */
+ public TestRepositoryResolver(TestRepository<Repository> repo, String repoName) {
+ this.repo = repo;
+ this.repoName = repoName;
+ }
+
+ @Override
+ public Repository open(HttpServletRequest req, String name)
+ throws RepositoryNotFoundException, ServiceNotEnabledException {
+ if (!name.equals(repoName)) {
+ throw new RepositoryNotFoundException(name);
+ }
+ Repository db = repo.getRepository();
+ db.incrementOpen();
+ return db;
+ }
+}
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java
index 06bfd79..727f9ba 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010, Google Inc.
+ * Copyright (C) 2010, 2017 Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -60,12 +60,9 @@
import java.util.List;
import java.util.Map;
-import javax.servlet.http.HttpServletRequest;
-
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jgit.errors.NotSupportedException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.http.server.GitServlet;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.junit.http.AccessEvent;
@@ -84,8 +81,6 @@
import org.eclipse.jgit.transport.http.HttpConnectionFactory;
import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory;
import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
-import org.eclipse.jgit.transport.resolver.RepositoryResolver;
-import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -124,19 +119,7 @@ public void setUp() throws Exception {
ServletContextHandler app = server.addContext("/git");
GitServlet gs = new GitServlet();
- gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
- @Override
- public Repository open(HttpServletRequest req, String name)
- throws RepositoryNotFoundException,
- ServiceNotEnabledException {
- if (!name.equals(srcName))
- throw new RepositoryNotFoundException(name);
-
- final Repository db = src.getRepository();
- db.incrementOpen();
- return db;
- }
- });
+ gs.setRepositoryResolver(new TestRepositoryResolver(src, srcName));
app.addServlet(new ServletHolder(gs), "/*");
server.setUp();
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerSslTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerSslTest.java
new file mode 100644
index 0000000..7deb0d8
--- /dev/null
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerSslTest.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2017 Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.http.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.errors.UnsupportedCredentialItem;
+import org.eclipse.jgit.http.server.GitServlet;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.junit.http.AccessEvent;
+import org.eclipse.jgit.junit.http.AppServer;
+import org.eclipse.jgit.junit.http.HttpTestCase;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.HttpTransport;
+import org.eclipse.jgit.transport.Transport;
+import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
+import org.eclipse.jgit.transport.http.HttpConnectionFactory;
+import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory;
+import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
+import org.eclipse.jgit.util.HttpSupport;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SmartClientSmartServerSslTest extends HttpTestCase {
+
+ // We run these tests with a server on localhost with a self-signed
+ // certificate. We don't do authentication tests here, so there's no need
+ // for username and password.
+ //
+ // But the server certificate will not validate. We know that Transport will
+ // ask whether we trust the server all the same. This credentials provider
+ // blindly trusts the self-signed certificate by answering "Yes" to all
+ // questions.
+ private CredentialsProvider testCredentials = new CredentialsProvider() {
+
+ @Override
+ public boolean isInteractive() {
+ return false;
+ }
+
+ @Override
+ public boolean supports(CredentialItem... items) {
+ for (CredentialItem item : items) {
+ if (item instanceof CredentialItem.InformationalMessage) {
+ continue;
+ }
+ if (item instanceof CredentialItem.YesNoType) {
+ continue;
+ }
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean get(URIish uri, CredentialItem... items)
+ throws UnsupportedCredentialItem {
+ for (CredentialItem item : items) {
+ if (item instanceof CredentialItem.InformationalMessage) {
+ continue;
+ }
+ if (item instanceof CredentialItem.YesNoType) {
+ ((CredentialItem.YesNoType) item).setValue(true);
+ continue;
+ }
+ return false;
+ }
+ return true;
+ }
+ };
+
+ private URIish remoteURI;
+
+ private URIish secureURI;
+
+ private RevBlob A_txt;
+
+ private RevCommit A, B;
+
+ @Parameters
+ public static Collection<Object[]> data() {
+ // run all tests with both connection factories we have
+ return Arrays.asList(new Object[][] {
+ { new JDKHttpConnectionFactory() },
+ { new HttpClientConnectionFactory() } });
+ }
+
+ public SmartClientSmartServerSslTest(HttpConnectionFactory cf) {
+ HttpTransport.setConnectionFactory(cf);
+ }
+
+ @Override
+ protected AppServer createServer() {
+ return new AppServer(0, 0);
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ final TestRepository<Repository> src = createTestRepository();
+ final String srcName = src.getRepository().getDirectory().getName();
+ src.getRepository()
+ .getConfig()
+ .setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
+
+ GitServlet gs = new GitServlet();
+
+ ServletContextHandler app = addNormalContext(gs, src, srcName);
+
+ server.setUp();
+
+ remoteURI = toURIish(app, srcName);
+ secureURI = new URIish(rewriteUrl(remoteURI.toString(), "https",
+ server.getSecurePort()));
+
+ A_txt = src.blob("A");
+ A = src.commit().add("A_txt", A_txt).create();
+ B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create();
+ src.update(master, B);
+
+ src.update("refs/garbage/a/very/long/ref/name/to/compress", B);
+ }
+
+ private ServletContextHandler addNormalContext(GitServlet gs, TestRepository<Repository> src, String srcName) {
+ ServletContextHandler app = server.addContext("/git");
+ app.addFilter(new FilterHolder(new Filter() {
+
+ @Override
+ public void init(FilterConfig filterConfig)
+ throws ServletException {
+ // empty
+ }
+
+ // Redirects http to https for requests containing "/https/".
+ @Override
+ public void doFilter(ServletRequest request,
+ ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
+ final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+ final StringBuffer fullUrl = httpServletRequest.getRequestURL();
+ if (httpServletRequest.getQueryString() != null) {
+ fullUrl.append("?")
+ .append(httpServletRequest.getQueryString());
+ }
+ String urlString = rewriteUrl(fullUrl.toString(), "https",
+ server.getSecurePort());
+ httpServletResponse
+ .setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+ httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
+ urlString.replace("/https/", "/"));
+ }
+
+ @Override
+ public void destroy() {
+ // empty
+ }
+ }), "/https/*", EnumSet.of(DispatcherType.REQUEST));
+ app.addFilter(new FilterHolder(new Filter() {
+
+ @Override
+ public void init(FilterConfig filterConfig)
+ throws ServletException {
+ // empty
+ }
+
+ // Redirects https back to http for requests containing "/back/".
+ @Override
+ public void doFilter(ServletRequest request,
+ ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
+ final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+ final StringBuffer fullUrl = httpServletRequest.getRequestURL();
+ if (httpServletRequest.getQueryString() != null) {
+ fullUrl.append("?")
+ .append(httpServletRequest.getQueryString());
+ }
+ String urlString = rewriteUrl(fullUrl.toString(), "http",
+ server.getPort());
+ httpServletResponse
+ .setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+ httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
+ urlString.replace("/back/", "/"));
+ }
+
+ @Override
+ public void destroy() {
+ // empty
+ }
+ }), "/back/*", EnumSet.of(DispatcherType.REQUEST));
+ gs.setRepositoryResolver(new TestRepositoryResolver(src, srcName));
+ app.addServlet(new ServletHolder(gs), "/*");
+ return app;
+ }
+
+ @Test
+ public void testInitialClone_ViaHttps() throws Exception {
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ try (Transport t = Transport.open(dst, secureURI)) {
+ t.setCredentialsProvider(testCredentials);
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ }
+ assertTrue(dst.hasObject(A_txt));
+ assertEquals(B, dst.exactRef(master).getObjectId());
+ fsck(dst, B);
+
+ List<AccessEvent> requests = getRequests();
+ assertEquals(2, requests.size());
+ }
+
+ @Test
+ public void testInitialClone_RedirectToHttps() throws Exception {
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ URIish cloneFrom = extendPath(remoteURI, "/https");
+ try (Transport t = Transport.open(dst, cloneFrom)) {
+ t.setCredentialsProvider(testCredentials);
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ }
+ assertTrue(dst.hasObject(A_txt));
+ assertEquals(B, dst.exactRef(master).getObjectId());
+ fsck(dst, B);
+
+ List<AccessEvent> requests = getRequests();
+ assertEquals(3, requests.size());
+ }
+
+ @Test
+ public void testInitialClone_RedirectBackToHttp() throws Exception {
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ URIish cloneFrom = extendPath(secureURI, "/back");
+ try (Transport t = Transport.open(dst, cloneFrom)) {
+ t.setCredentialsProvider(testCredentials);
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ fail("Should have failed (redirect from https to http)");
+ } catch (TransportException e) {
+ assertTrue(e.getMessage().contains("not allowed"));
+ }
+ }
+
+ @Test
+ public void testInitialClone_SslFailure() throws Exception {
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ try (Transport t = Transport.open(dst, secureURI)) {
+ // Set a credentials provider that doesn't handle questions
+ t.setCredentialsProvider(
+ new UsernamePasswordCredentialsProvider("any", "anypwd"));
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ fail("Should have failed (SSL certificate not trusted)");
+ } catch (TransportException e) {
+ assertTrue(e.getMessage().contains("Secure connection"));
+ }
+ }
+
+}
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
index ed223c9..caa172e 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010, Google Inc.
+ * Copyright (C) 2010, 2017 Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -57,17 +57,21 @@
import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
+import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@@ -78,9 +82,10 @@
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jgit.errors.RemoteRepositoryException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.errors.UnsupportedCredentialItem;
import org.eclipse.jgit.http.server.GitServlet;
+import org.eclipse.jgit.http.server.resolver.DefaultUploadPackFactory;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
import org.eclipse.jgit.junit.TestRepository;
@@ -101,20 +106,35 @@
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.transport.AbstractAdvertiseRefsHook;
+import org.eclipse.jgit.transport.AdvertiseRefsHook;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.FetchConnection;
import org.eclipse.jgit.transport.HttpTransport;
+import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.transport.Transport;
import org.eclipse.jgit.transport.TransportHttp;
import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.transport.UploadPack;
+import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.transport.http.HttpConnectionFactory;
import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory;
import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
-import org.eclipse.jgit.transport.resolver.RepositoryResolver;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.eclipse.jgit.transport.resolver.UploadPackFactory;
+import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.HttpSupport;
+import org.eclipse.jgit.util.SystemReader;
+import org.hamcrest.Matchers;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@@ -123,17 +143,29 @@
public class SmartClientSmartServerTest extends HttpTestCase {
private static final String HDR_TRANSFER_ENCODING = "Transfer-Encoding";
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private AdvertiseRefsHook advertiseRefsHook;
+
private Repository remoteRepository;
+ private CredentialsProvider testCredentials = new UsernamePasswordCredentialsProvider(
+ AppServer.username, AppServer.password);
+
private URIish remoteURI;
private URIish brokenURI;
private URIish redirectURI;
+ private URIish authURI;
+
+ private URIish authOnPostURI;
+
private RevBlob A_txt;
- private RevCommit A, B;
+ private RevCommit A, B, unreachableCommit;
@Parameters
public static Collection<Object[]> data() {
@@ -160,12 +192,29 @@ public void setUp() throws Exception {
ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
GitServlet gs = new GitServlet();
+ gs.setUploadPackFactory(new UploadPackFactory<HttpServletRequest>() {
+ @Override
+ public UploadPack create(HttpServletRequest req, Repository db)
+ throws ServiceNotEnabledException,
+ ServiceNotAuthorizedException {
+ DefaultUploadPackFactory f = new DefaultUploadPackFactory();
+ UploadPack up = f.create(req, db);
+ if (advertiseRefsHook != null) {
+ up.setAdvertiseRefsHook(advertiseRefsHook);
+ }
+ return up;
+ }
+ });
ServletContextHandler app = addNormalContext(gs, src, srcName);
ServletContextHandler broken = addBrokenContext(gs, src, srcName);
- ServletContextHandler redirect = addRedirectContext(gs, src, srcName);
+ ServletContextHandler redirect = addRedirectContext(gs);
+
+ ServletContextHandler auth = addAuthContext(gs, "auth");
+
+ ServletContextHandler authOnPost = addAuthContext(gs, "pauth", "POST");
server.setUp();
@@ -173,18 +222,67 @@ public void setUp() throws Exception {
remoteURI = toURIish(app, srcName);
brokenURI = toURIish(broken, srcName);
redirectURI = toURIish(redirect, srcName);
+ authURI = toURIish(auth, srcName);
+ authOnPostURI = toURIish(authOnPost, srcName);
A_txt = src.blob("A");
A = src.commit().add("A_txt", A_txt).create();
B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create();
src.update(master, B);
+ unreachableCommit = src.commit().add("A_txt", A_txt).create();
+
src.update("refs/garbage/a/very/long/ref/name/to/compress", B);
}
private ServletContextHandler addNormalContext(GitServlet gs, TestRepository<Repository> src, String srcName) {
ServletContextHandler app = server.addContext("/git");
- gs.setRepositoryResolver(new TestRepoResolver(src, srcName));
+ app.addFilter(new FilterHolder(new Filter() {
+
+ @Override
+ public void init(FilterConfig filterConfig)
+ throws ServletException {
+ // empty
+ }
+
+ // Does an internal forward for GET requests containing "/post/",
+ // and issues a 301 redirect on POST requests for such URLs. Used
+ // in the POST redirect tests.
+ @Override
+ public void doFilter(ServletRequest request,
+ ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
+ final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+ final StringBuffer fullUrl = httpServletRequest.getRequestURL();
+ if (httpServletRequest.getQueryString() != null) {
+ fullUrl.append("?")
+ .append(httpServletRequest.getQueryString());
+ }
+ String urlString = fullUrl.toString();
+ if ("POST".equalsIgnoreCase(httpServletRequest.getMethod())) {
+ httpServletResponse.setStatus(
+ HttpServletResponse.SC_MOVED_PERMANENTLY);
+ httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
+ urlString.replace("/post/", "/"));
+ } else {
+ String path = httpServletRequest.getPathInfo();
+ path = path.replace("/post/", "/");
+ if (httpServletRequest.getQueryString() != null) {
+ path += '?' + httpServletRequest.getQueryString();
+ }
+ RequestDispatcher dispatcher = httpServletRequest
+ .getRequestDispatcher(path);
+ dispatcher.forward(httpServletRequest, httpServletResponse);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ // empty
+ }
+ }), "/post/*", EnumSet.of(DispatcherType.REQUEST));
+ gs.setRepositoryResolver(new TestRepositoryResolver(src, srcName));
app.addServlet(new ServletHolder(gs), "/*");
return app;
}
@@ -222,12 +320,28 @@ public void destroy() {
return broken;
}
- @SuppressWarnings("unused")
- private ServletContextHandler addRedirectContext(GitServlet gs,
- TestRepository<Repository> src, String srcName) {
+ private ServletContextHandler addAuthContext(GitServlet gs,
+ String contextPath, String... methods) {
+ ServletContextHandler auth = server.addContext('/' + contextPath);
+ auth.addServlet(new ServletHolder(gs), "/*");
+ return server.authBasic(auth, methods);
+ }
+
+ private ServletContextHandler addRedirectContext(GitServlet gs) {
ServletContextHandler redirect = server.addContext("/redirect");
redirect.addFilter(new FilterHolder(new Filter() {
+ // Enables tests for different codes, and for multiple redirects.
+ // First parameter is the number of redirects, second one is the
+ // redirect status code that should be used
+ private Pattern responsePattern = Pattern
+ .compile("/response/(\\d+)/(30[1237])/");
+
+ // Enables tests to specify the context that the request should be
+ // redirected to in the end. If not present, redirects got to the
+ // normal /git context.
+ private Pattern targetPattern = Pattern.compile("/target(/\\w+)/");
+
@Override
public void init(FilterConfig filterConfig)
throws ServletException {
@@ -245,10 +359,50 @@ public void doFilter(ServletRequest request,
fullUrl.append("?")
.append(httpServletRequest.getQueryString());
}
- httpServletResponse
- .setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+ String urlString = fullUrl.toString();
+ if (urlString.contains("/loop/")) {
+ urlString = urlString.replace("/loop/", "/loop/x/");
+ if (urlString.contains("/loop/x/x/x/x/x/x/x/x/")) {
+ // Go back to initial.
+ urlString = urlString.replace("/loop/x/x/x/x/x/x/x/x/",
+ "/loop/");
+ }
+ httpServletResponse.setStatus(
+ HttpServletResponse.SC_MOVED_TEMPORARILY);
+ httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
+ urlString);
+ return;
+ }
+ int responseCode = HttpServletResponse.SC_MOVED_PERMANENTLY;
+ int nofRedirects = 0;
+ Matcher matcher = responsePattern.matcher(urlString);
+ if (matcher.find()) {
+ nofRedirects = Integer
+ .parseUnsignedInt(matcher.group(1));
+ responseCode = Integer.parseUnsignedInt(matcher.group(2));
+ if (--nofRedirects <= 0) {
+ urlString = urlString.substring(0, matcher.start())
+ + '/' + urlString.substring(matcher.end());
+ } else {
+ urlString = urlString.substring(0, matcher.start())
+ + "/response/" + nofRedirects + "/"
+ + responseCode + '/'
+ + urlString.substring(matcher.end());
+ }
+ }
+ httpServletResponse.setStatus(responseCode);
+ if (nofRedirects <= 0) {
+ String targetContext = "/git";
+ matcher = targetPattern.matcher(urlString);
+ if (matcher.find()) {
+ urlString = urlString.substring(0, matcher.start())
+ + '/' + urlString.substring(matcher.end());
+ targetContext = matcher.group(1);
+ }
+ urlString = urlString.replace("/redirect", targetContext);
+ }
httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
- fullUrl.toString().replace("/redirect", "/git"));
+ urlString);
}
@Override
@@ -334,6 +488,56 @@ public void testListRemote_BadName() throws IOException, URISyntaxException {
}
@Test
+ public void testFetchBySHA1() throws Exception {
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ try (Transport t = Transport.open(dst, remoteURI)) {
+ t.fetch(NullProgressMonitor.INSTANCE,
+ Collections.singletonList(new RefSpec(B.name())));
+ }
+
+ assertTrue(dst.hasObject(A_txt));
+ }
+
+ @Test
+ public void testFetchBySHA1Unreachable() throws Exception {
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ try (Transport t = Transport.open(dst, remoteURI)) {
+ thrown.expect(TransportException.class);
+ thrown.expectMessage(Matchers.containsString(
+ "want " + unreachableCommit.name() + " not valid"));
+ t.fetch(NullProgressMonitor.INSTANCE, Collections
+ .singletonList(new RefSpec(unreachableCommit.name())));
+ }
+ }
+
+ @Test
+ public void testFetchBySHA1UnreachableByAdvertiseRefsHook()
+ throws Exception {
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ advertiseRefsHook = new AbstractAdvertiseRefsHook() {
+ @Override
+ protected Map<String, Ref> getAdvertisedRefs(Repository repository,
+ RevWalk revWalk) {
+ return Collections.emptyMap();
+ }
+ };
+
+ try (Transport t = Transport.open(dst, remoteURI)) {
+ thrown.expect(TransportException.class);
+ thrown.expectMessage(Matchers.containsString(
+ "want " + A.name() + " not valid"));
+ t.fetch(NullProgressMonitor.INSTANCE, Collections
+ .singletonList(new RefSpec(A.name())));
+ }
+ }
+
+ @Test
public void testInitialClone_Small() throws Exception {
Repository dst = createBareRepository();
assertFalse(dst.hasObject(A_txt));
@@ -373,13 +577,332 @@ public void testInitialClone_Small() throws Exception {
.getResponseHeader(HDR_CONTENT_TYPE));
}
+ private void initialClone_Redirect(int nofRedirects, int code)
+ throws Exception {
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ URIish cloneFrom = redirectURI;
+ if (code != 301 || nofRedirects > 1) {
+ cloneFrom = extendPath(cloneFrom,
+ "/response/" + nofRedirects + "/" + code);
+ }
+ try (Transport t = Transport.open(dst, cloneFrom)) {
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ }
+
+ assertTrue(dst.hasObject(A_txt));
+ assertEquals(B, dst.exactRef(master).getObjectId());
+ fsck(dst, B);
+
+ List<AccessEvent> requests = getRequests();
+ assertEquals(2 + nofRedirects, requests.size());
+
+ int n = 0;
+ while (n < nofRedirects) {
+ AccessEvent redirect = requests.get(n++);
+ assertEquals(code, redirect.getStatus());
+ }
+
+ AccessEvent info = requests.get(n++);
+ assertEquals("GET", info.getMethod());
+ assertEquals(join(remoteURI, "info/refs"), info.getPath());
+ assertEquals(1, info.getParameters().size());
+ assertEquals("git-upload-pack", info.getParameter("service"));
+ assertEquals(200, info.getStatus());
+ assertEquals("application/x-git-upload-pack-advertisement",
+ info.getResponseHeader(HDR_CONTENT_TYPE));
+ assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
+
+ AccessEvent service = requests.get(n++);
+ assertEquals("POST", service.getMethod());
+ assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
+ assertEquals(0, service.getParameters().size());
+ assertNotNull("has content-length",
+ service.getRequestHeader(HDR_CONTENT_LENGTH));
+ assertNull("not chunked",
+ service.getRequestHeader(HDR_TRANSFER_ENCODING));
+
+ assertEquals(200, service.getStatus());
+ assertEquals("application/x-git-upload-pack-result",
+ service.getResponseHeader(HDR_CONTENT_TYPE));
+ }
+
@Test
- public void testInitialClone_RedirectSmall() throws Exception {
+ public void testInitialClone_Redirect301Small() throws Exception {
+ initialClone_Redirect(1, 301);
+ }
+
+ @Test
+ public void testInitialClone_Redirect302Small() throws Exception {
+ initialClone_Redirect(1, 302);
+ }
+
+ @Test
+ public void testInitialClone_Redirect303Small() throws Exception {
+ initialClone_Redirect(1, 303);
+ }
+
+ @Test
+ public void testInitialClone_Redirect307Small() throws Exception {
+ initialClone_Redirect(1, 307);
+ }
+
+ @Test
+ public void testInitialClone_RedirectMultiple() throws Exception {
+ initialClone_Redirect(4, 302);
+ }
+
+ @Test
+ public void testInitialClone_RedirectMax() throws Exception {
+ FileBasedConfig userConfig = SystemReader.getInstance()
+ .openUserConfig(null, FS.DETECTED);
+ userConfig.setInt("http", null, "maxRedirects", 4);
+ userConfig.save();
+ initialClone_Redirect(4, 302);
+ }
+
+ @Test
+ public void testInitialClone_RedirectTooOften() throws Exception {
+ FileBasedConfig userConfig = SystemReader.getInstance()
+ .openUserConfig(null, FS.DETECTED);
+ userConfig.setInt("http", null, "maxRedirects", 3);
+ userConfig.save();
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ URIish cloneFrom = extendPath(redirectURI, "/response/4/302");
+ String remoteUri = cloneFrom.toString();
+ try (Transport t = Transport.open(dst, cloneFrom)) {
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ fail("Should have failed (too many redirects)");
+ } catch (TransportException e) {
+ String expectedMessageBegin = remoteUri.toString() + ": "
+ + MessageFormat.format(JGitText.get().redirectLimitExceeded,
+ "3", remoteUri.replace("/4/", "/1/") + '/', "");
+ String message = e.getMessage();
+ if (message.length() > expectedMessageBegin.length()) {
+ message = message.substring(0, expectedMessageBegin.length());
+ }
+ assertEquals(expectedMessageBegin, message);
+ }
+ }
+
+ @Test
+ public void testInitialClone_RedirectLoop() throws Exception {
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ URIish cloneFrom = extendPath(redirectURI, "/loop");
+ try (Transport t = Transport.open(dst, cloneFrom)) {
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ fail("Should have failed (redirect loop)");
+ } catch (TransportException e) {
+ assertTrue(e.getMessage().contains("Redirected more than"));
+ }
+ }
+
+ @Test
+ public void testInitialClone_RedirectOnPostAllowed() throws Exception {
+ FileBasedConfig userConfig = SystemReader.getInstance()
+ .openUserConfig(null, FS.DETECTED);
+ userConfig.setString("http", null, "followRedirects", "true");
+ userConfig.save();
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ URIish cloneFrom = extendPath(remoteURI, "/post");
+ try (Transport t = Transport.open(dst, cloneFrom)) {
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ }
+
+ assertTrue(dst.hasObject(A_txt));
+ assertEquals(B, dst.exactRef(master).getObjectId());
+ fsck(dst, B);
+
+ List<AccessEvent> requests = getRequests();
+ assertEquals(3, requests.size());
+
+ AccessEvent info = requests.get(0);
+ assertEquals("GET", info.getMethod());
+ assertEquals(join(cloneFrom, "info/refs"), info.getPath());
+ assertEquals(1, info.getParameters().size());
+ assertEquals("git-upload-pack", info.getParameter("service"));
+ assertEquals(200, info.getStatus());
+ assertEquals("application/x-git-upload-pack-advertisement",
+ info.getResponseHeader(HDR_CONTENT_TYPE));
+ assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
+
+ AccessEvent redirect = requests.get(1);
+ assertEquals("POST", redirect.getMethod());
+ assertEquals(301, redirect.getStatus());
+
+ AccessEvent service = requests.get(2);
+ assertEquals("POST", service.getMethod());
+ assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
+ assertEquals(0, service.getParameters().size());
+ assertNotNull("has content-length",
+ service.getRequestHeader(HDR_CONTENT_LENGTH));
+ assertNull("not chunked",
+ service.getRequestHeader(HDR_TRANSFER_ENCODING));
+
+ assertEquals(200, service.getStatus());
+ assertEquals("application/x-git-upload-pack-result",
+ service.getResponseHeader(HDR_CONTENT_TYPE));
+ }
+
+ @Test
+ public void testInitialClone_RedirectOnPostForbidden() throws Exception {
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ URIish cloneFrom = extendPath(remoteURI, "/post");
+ try (Transport t = Transport.open(dst, cloneFrom)) {
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ fail("Should have failed (redirect on POST)");
+ } catch (TransportException e) {
+ assertTrue(e.getMessage().contains("301"));
+ }
+ }
+
+ @Test
+ public void testInitialClone_RedirectForbidden() throws Exception {
+ FileBasedConfig userConfig = SystemReader.getInstance()
+ .openUserConfig(null, FS.DETECTED);
+ userConfig.setString("http", null, "followRedirects", "false");
+ userConfig.save();
+
Repository dst = createBareRepository();
assertFalse(dst.hasObject(A_txt));
try (Transport t = Transport.open(dst, redirectURI)) {
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ fail("Should have failed (redirects forbidden)");
+ } catch (TransportException e) {
+ assertTrue(
+ e.getMessage().contains("http.followRedirects is false"));
+ }
+ }
+
+ @Test
+ public void testInitialClone_WithAuthentication() throws Exception {
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ try (Transport t = Transport.open(dst, authURI)) {
+ t.setCredentialsProvider(testCredentials);
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ }
+
+ assertTrue(dst.hasObject(A_txt));
+ assertEquals(B, dst.exactRef(master).getObjectId());
+ fsck(dst, B);
+
+ List<AccessEvent> requests = getRequests();
+ assertEquals(3, requests.size());
+
+ AccessEvent info = requests.get(0);
+ assertEquals("GET", info.getMethod());
+ assertEquals(401, info.getStatus());
+
+ info = requests.get(1);
+ assertEquals("GET", info.getMethod());
+ assertEquals(join(authURI, "info/refs"), info.getPath());
+ assertEquals(1, info.getParameters().size());
+ assertEquals("git-upload-pack", info.getParameter("service"));
+ assertEquals(200, info.getStatus());
+ assertEquals("application/x-git-upload-pack-advertisement",
+ info.getResponseHeader(HDR_CONTENT_TYPE));
+ assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
+
+ AccessEvent service = requests.get(2);
+ assertEquals("POST", service.getMethod());
+ assertEquals(join(authURI, "git-upload-pack"), service.getPath());
+ assertEquals(0, service.getParameters().size());
+ assertNotNull("has content-length",
+ service.getRequestHeader(HDR_CONTENT_LENGTH));
+ assertNull("not chunked",
+ service.getRequestHeader(HDR_TRANSFER_ENCODING));
+
+ assertEquals(200, service.getStatus());
+ assertEquals("application/x-git-upload-pack-result",
+ service.getResponseHeader(HDR_CONTENT_TYPE));
+ }
+
+ @Test
+ public void testInitialClone_WithAuthenticationNoCredentials()
+ throws Exception {
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ try (Transport t = Transport.open(dst, authURI)) {
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ fail("Should not have succeeded -- no authentication");
+ } catch (TransportException e) {
+ String msg = e.getMessage();
+ assertTrue("Unexpected exception message: " + msg,
+ msg.contains("no CredentialsProvider"));
+ }
+ List<AccessEvent> requests = getRequests();
+ assertEquals(1, requests.size());
+
+ AccessEvent info = requests.get(0);
+ assertEquals("GET", info.getMethod());
+ assertEquals(401, info.getStatus());
+ }
+
+ @Test
+ public void testInitialClone_WithAuthenticationWrongCredentials()
+ throws Exception {
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ try (Transport t = Transport.open(dst, authURI)) {
+ t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
+ AppServer.username, "wrongpassword"));
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ fail("Should not have succeeded -- wrong password");
+ } catch (TransportException e) {
+ String msg = e.getMessage();
+ assertTrue("Unexpected exception message: " + msg,
+ msg.contains("auth"));
+ }
+ List<AccessEvent> requests = getRequests();
+ // Once without authentication plus three re-tries with authentication
+ assertEquals(4, requests.size());
+
+ for (AccessEvent event : requests) {
+ assertEquals("GET", event.getMethod());
+ assertEquals(401, event.getStatus());
+ }
+ }
+
+ @Test
+ public void testInitialClone_WithAuthenticationAfterRedirect()
+ throws Exception {
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ URIish cloneFrom = extendPath(redirectURI, "/target/auth");
+ CredentialsProvider uriSpecificCredentialsProvider = new UsernamePasswordCredentialsProvider(
+ "unknown", "none") {
+ @Override
+ public boolean get(URIish uri, CredentialItem... items)
+ throws UnsupportedCredentialItem {
+ // Only return the true credentials if the uri path starts with
+ // /auth. This ensures that we do provide the correct
+ // credentials only for the URi after the redirect, making the
+ // test fail if we should be asked for the credentials for the
+ // original URI.
+ if (uri.getPath().startsWith("/auth")) {
+ return testCredentials.get(uri, items);
+ }
+ return super.get(uri, items);
+ }
+ };
+ try (Transport t = Transport.open(dst, cloneFrom)) {
+ t.setCredentialsProvider(uriSpecificCredentialsProvider);
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
}
assertTrue(dst.hasObject(A_txt));
@@ -389,12 +912,19 @@ public void testInitialClone_RedirectSmall() throws Exception {
List<AccessEvent> requests = getRequests();
assertEquals(4, requests.size());
- AccessEvent firstRedirect = requests.get(0);
- assertEquals(301, firstRedirect.getStatus());
+ AccessEvent redirect = requests.get(0);
+ assertEquals("GET", redirect.getMethod());
+ assertEquals(join(cloneFrom, "info/refs"), redirect.getPath());
+ assertEquals(301, redirect.getStatus());
AccessEvent info = requests.get(1);
assertEquals("GET", info.getMethod());
- assertEquals(join(remoteURI, "info/refs"), info.getPath());
+ assertEquals(join(authURI, "info/refs"), info.getPath());
+ assertEquals(401, info.getStatus());
+
+ info = requests.get(2);
+ assertEquals("GET", info.getMethod());
+ assertEquals(join(authURI, "info/refs"), info.getPath());
assertEquals(1, info.getParameters().size());
assertEquals("git-upload-pack", info.getParameter("service"));
assertEquals(200, info.getStatus());
@@ -402,12 +932,56 @@ public void testInitialClone_RedirectSmall() throws Exception {
info.getResponseHeader(HDR_CONTENT_TYPE));
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
- AccessEvent secondRedirect = requests.get(2);
- assertEquals(301, secondRedirect.getStatus());
-
AccessEvent service = requests.get(3);
assertEquals("POST", service.getMethod());
- assertEquals(join(remoteURI, "git-upload-pack"), service.getPath());
+ assertEquals(join(authURI, "git-upload-pack"), service.getPath());
+ assertEquals(0, service.getParameters().size());
+ assertNotNull("has content-length",
+ service.getRequestHeader(HDR_CONTENT_LENGTH));
+ assertNull("not chunked",
+ service.getRequestHeader(HDR_TRANSFER_ENCODING));
+
+ assertEquals(200, service.getStatus());
+ assertEquals("application/x-git-upload-pack-result",
+ service.getResponseHeader(HDR_CONTENT_TYPE));
+ }
+
+ @Test
+ public void testInitialClone_WithAuthenticationOnPostOnly()
+ throws Exception {
+ Repository dst = createBareRepository();
+ assertFalse(dst.hasObject(A_txt));
+
+ try (Transport t = Transport.open(dst, authOnPostURI)) {
+ t.setCredentialsProvider(testCredentials);
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ }
+
+ assertTrue(dst.hasObject(A_txt));
+ assertEquals(B, dst.exactRef(master).getObjectId());
+ fsck(dst, B);
+
+ List<AccessEvent> requests = getRequests();
+ assertEquals(3, requests.size());
+
+ AccessEvent info = requests.get(0);
+ assertEquals("GET", info.getMethod());
+ assertEquals(join(authOnPostURI, "info/refs"), info.getPath());
+ assertEquals(1, info.getParameters().size());
+ assertEquals("git-upload-pack", info.getParameter("service"));
+ assertEquals(200, info.getStatus());
+ assertEquals("application/x-git-upload-pack-advertisement",
+ info.getResponseHeader(HDR_CONTENT_TYPE));
+ assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
+
+ AccessEvent service = requests.get(1);
+ assertEquals("POST", service.getMethod());
+ assertEquals(join(authOnPostURI, "git-upload-pack"), service.getPath());
+ assertEquals(401, service.getStatus());
+
+ service = requests.get(2);
+ assertEquals("POST", service.getMethod());
+ assertEquals(join(authOnPostURI, "git-upload-pack"), service.getPath());
assertEquals(0, service.getParameters().size());
assertNotNull("has content-length",
service.getRequestHeader(HDR_CONTENT_LENGTH));
@@ -619,7 +1193,7 @@ public void testFetch_RefsUnreadableOnUpload() throws Exception {
ServletContextHandler app = noRefServer.addContext("/git");
GitServlet gs = new GitServlet();
- gs.setRepositoryResolver(new TestRepoResolver(repo, repoName));
+ gs.setRepositoryResolver(new TestRepositoryResolver(repo, repoName));
app.addServlet(new ServletHolder(gs), "/*");
noRefServer.setUp();
@@ -822,28 +1396,4 @@ private void enableReceivePack() throws IOException {
cfg.save();
}
- private final class TestRepoResolver
- implements RepositoryResolver<HttpServletRequest> {
-
- private final TestRepository<Repository> repo;
-
- private final String repoName;
-
- private TestRepoResolver(TestRepository<Repository> repo,
- String repoName) {
- this.repo = repo;
- this.repoName = repoName;
- }
-
- @Override
- public Repository open(HttpServletRequest req, String name)
- throws RepositoryNotFoundException, ServiceNotEnabledException {
- if (!name.equals(repoName))
- throw new RepositoryNotFoundException(name);
-
- Repository db = repo.getRepository();
- db.incrementOpen();
- return db;
- }
- }
}
diff --git a/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.junit.http/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.junit.http/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.junit.http/.settings/org.eclipse.pde.api.tools.prefs
index cd148d9..c0030de 100644
--- a/org.eclipse.jgit.junit.http/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.junit.http/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,4 +1,4 @@
-#Tue Oct 18 00:52:01 CEST 2011
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -8,6 +8,10 @@
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -47,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -58,6 +63,7 @@
INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
INVALID_JAVADOC_TAG=Ignore
INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
LEAK_EXTEND=Warning
@@ -75,6 +81,7 @@
METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
@@ -83,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
index 00a7a65..dd12de4 100644
--- a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit.junit.http
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-Localization: plugin
Bundle-Vendor: %provider_name
Bundle-ActivationPolicy: lazy
@@ -20,16 +20,17 @@
org.eclipse.jetty.util.component;version="[9.4.5,10.0.0)",
org.eclipse.jetty.util.log;version="[9.4.5,10.0.0)",
org.eclipse.jetty.util.security;version="[9.4.5,10.0.0)",
- org.eclipse.jgit.errors;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.http.server;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.junit;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport.resolver;version="[4.8.1,4.9.0)",
+ org.eclipse.jetty.util.ssl;version="[9.4.5,10.0.0)",
+ org.eclipse.jgit.errors;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.http.server;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.junit;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.revwalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.9.9,4.10.0)",
org.junit;version="[4.0.0,5.0.0)"
-Export-Package: org.eclipse.jgit.junit.http;version="4.8.1";
+Export-Package: org.eclipse.jgit.junit.http;version="4.9.9";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.junit,
javax.servlet.http,
diff --git a/org.eclipse.jgit.junit.http/pom.xml b/org.eclipse.jgit.junit.http/pom.xml
index 0c1acf9..8a9e7cc 100644
--- a/org.eclipse.jgit.junit.http/pom.xml
+++ b/org.eclipse.jgit.junit.http/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.junit.http</artifactId>
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java
index 28c0f21..e257cf6 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010, 2012 Google Inc.
+ * Copyright (C) 2010, 2017 Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -46,15 +46,20 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import java.io.File;
+import java.io.IOException;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
+import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.security.AbstractLoginService;
import org.eclipse.jetty.security.Authenticator;
import org.eclipse.jetty.security.ConstraintMapping;
@@ -65,10 +70,12 @@
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.security.Password;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jgit.transport.URIish;
/**
@@ -88,6 +95,12 @@ public class AppServer {
/** Password for {@link #username} in secured access areas. */
public static final String password = "letmein";
+ /** SSL keystore password; must have at least 6 characters. */
+ private static final String keyPassword = "mykeys";
+
+ /** Role for authentication. */
+ private static final String authRole = "can-access";
+
static {
// Install a logger that throws warning messages.
//
@@ -97,48 +110,141 @@ public class AppServer {
private final Server server;
+ private final HttpConfiguration config;
+
private final ServerConnector connector;
+ private final HttpConfiguration secureConfig;
+
+ private final ServerConnector secureConnector;
+
private final ContextHandlerCollection contexts;
private final TestRequestLog log;
+ private List<File> filesToDelete = new ArrayList<>();
+
public AppServer() {
- this(0);
+ this(0, -1);
}
/**
* @param port
- * the http port number
+ * the http port number; may be zero to allocate a port
+ * dynamically
* @since 4.2
*/
public AppServer(int port) {
+ this(port, -1);
+ }
+
+ /**
+ * @param port
+ * for http, may be zero to allocate a port dynamically
+ * @param sslPort
+ * for https,may be zero to allocate a port dynamically. If
+ * negative, the server will be set up without https support.
+ * @since 4.9
+ */
+ public AppServer(int port, int sslPort) {
server = new Server();
- HttpConfiguration http_config = new HttpConfiguration();
- http_config.setSecureScheme("https");
- http_config.setSecurePort(8443);
- http_config.setOutputBufferSize(32768);
+ config = new HttpConfiguration();
+ config.setSecureScheme("https");
+ config.setSecurePort(0);
+ config.setOutputBufferSize(32768);
connector = new ServerConnector(server,
- new HttpConnectionFactory(http_config));
+ new HttpConnectionFactory(config));
connector.setPort(port);
+ String ip;
+ String hostName;
try {
final InetAddress me = InetAddress.getByName("localhost");
- connector.setHost(me.getHostAddress());
+ ip = me.getHostAddress();
+ connector.setHost(ip);
+ hostName = InetAddress.getLocalHost().getCanonicalHostName();
} catch (UnknownHostException e) {
throw new RuntimeException("Cannot find localhost", e);
}
+ if (sslPort >= 0) {
+ SslContextFactory sslContextFactory = createTestSslContextFactory(
+ hostName);
+ secureConfig = new HttpConfiguration(config);
+ secureConnector = new ServerConnector(server,
+ new SslConnectionFactory(sslContextFactory,
+ HttpVersion.HTTP_1_1.asString()),
+ new HttpConnectionFactory(secureConfig));
+ secureConnector.setPort(sslPort);
+ secureConnector.setHost(ip);
+ } else {
+ secureConfig = null;
+ secureConnector = null;
+ }
+
contexts = new ContextHandlerCollection();
log = new TestRequestLog();
log.setHandler(contexts);
- server.setConnectors(new Connector[] { connector });
+ if (secureConnector == null) {
+ server.setConnectors(new Connector[] { connector });
+ } else {
+ server.setConnectors(
+ new Connector[] { connector, secureConnector });
+ }
server.setHandler(log);
}
+ private SslContextFactory createTestSslContextFactory(String hostName) {
+ SslContextFactory factory = new SslContextFactory(true);
+
+ String dName = "CN=,OU=,O=,ST=,L=,C=";
+
+ try {
+ File tmpDir = Files.createTempDirectory("jks").toFile();
+ tmpDir.deleteOnExit();
+ makePrivate(tmpDir);
+ File keyStore = new File(tmpDir, "keystore.jks");
+ Runtime.getRuntime().exec(
+ new String[] {
+ "keytool", //
+ "-keystore", keyStore.getAbsolutePath(), //
+ "-storepass", keyPassword,
+ "-alias", hostName, //
+ "-genkeypair", //
+ "-keyalg", "RSA", //
+ "-keypass", keyPassword, //
+ "-dname", dName, //
+ "-validity", "2" //
+ }).waitFor();
+ keyStore.deleteOnExit();
+ makePrivate(keyStore);
+ filesToDelete.add(keyStore);
+ filesToDelete.add(tmpDir);
+ factory.setKeyStorePath(keyStore.getAbsolutePath());
+ factory.setKeyStorePassword(keyPassword);
+ factory.setKeyManagerPassword(keyPassword);
+ factory.setTrustStorePath(keyStore.getAbsolutePath());
+ factory.setTrustStorePassword(keyPassword);
+ } catch (InterruptedException | IOException e) {
+ throw new RuntimeException("Cannot create ssl key/certificate", e);
+ }
+ return factory;
+ }
+
+ private void makePrivate(File file) {
+ file.setReadable(false);
+ file.setWritable(false);
+ file.setExecutable(false);
+ file.setReadable(true, true);
+ file.setWritable(true, true);
+ if (file.isDirectory()) {
+ file.setExecutable(true, true);
+ }
+ }
+
/**
* Create a new servlet context within the server.
* <p>
@@ -162,9 +268,10 @@ public ServletContextHandler addContext(String path) {
return ctx;
}
- public ServletContextHandler authBasic(ServletContextHandler ctx) {
+ public ServletContextHandler authBasic(ServletContextHandler ctx,
+ String... methods) {
assertNotYetSetUp();
- auth(ctx, new BasicAuthenticator());
+ auth(ctx, new BasicAuthenticator(), methods);
return ctx;
}
@@ -199,22 +306,36 @@ protected UserPrincipal loadUserInfo(String user) {
}
}
- private void auth(ServletContextHandler ctx, Authenticator authType) {
- final String role = "can-access";
-
- AbstractLoginService users = new TestMappedLoginService(role);
+ private ConstraintMapping createConstraintMapping() {
ConstraintMapping cm = new ConstraintMapping();
cm.setConstraint(new Constraint());
cm.getConstraint().setAuthenticate(true);
cm.getConstraint().setDataConstraint(Constraint.DC_NONE);
- cm.getConstraint().setRoles(new String[] { role });
+ cm.getConstraint().setRoles(new String[] { authRole });
cm.setPathSpec("/*");
+ return cm;
+ }
+
+ private void auth(ServletContextHandler ctx, Authenticator authType,
+ String... methods) {
+ AbstractLoginService users = new TestMappedLoginService(authRole);
+ List<ConstraintMapping> mappings = new ArrayList<>();
+ if (methods == null || methods.length == 0) {
+ mappings.add(createConstraintMapping());
+ } else {
+ for (String method : methods) {
+ ConstraintMapping cm = createConstraintMapping();
+ cm.setMethod(method.toUpperCase(Locale.ROOT));
+ mappings.add(cm);
+ }
+ }
ConstraintSecurityHandler sec = new ConstraintSecurityHandler();
sec.setRealmName(realm);
sec.setAuthenticator(authType);
sec.setLoginService(users);
- sec.setConstraintMappings(new ConstraintMapping[] { cm });
+ sec.setConstraintMappings(
+ mappings.toArray(new ConstraintMapping[mappings.size()]));
sec.setHandler(ctx);
contexts.removeHandler(ctx);
@@ -231,6 +352,10 @@ public void setUp() throws Exception {
RecordingLogger.clear();
log.clear();
server.start();
+ config.setSecurePort(getSecurePort());
+ if (secureConfig != null) {
+ secureConfig.setSecurePort(getSecurePort());
+ }
}
/**
@@ -243,6 +368,10 @@ public void tearDown() throws Exception {
RecordingLogger.clear();
log.clear();
server.stop();
+ for (File f : filesToDelete) {
+ f.delete();
+ }
+ filesToDelete.clear();
}
/**
@@ -272,6 +401,12 @@ public int getPort() {
return connector.getLocalPort();
}
+ /** @return the HTTPS port or -1 if not configured. */
+ public int getSecurePort() {
+ assertAlreadySetUp();
+ return secureConnector != null ? secureConnector.getLocalPort() : -1;
+ }
+
/** @return all requests since the server was started. */
public List<AccessEvent> getRequests() {
return new ArrayList<>(log.getEvents());
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java
index 1b94e02..eabb0f2 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/HttpTestCase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2010, Google Inc.
+ * Copyright (C) 2009-2017, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -77,7 +77,7 @@ public abstract class HttpTestCase extends LocalDiskRepositoryTestCase {
@Override
public void setUp() throws Exception {
super.setUp();
- server = new AppServer();
+ server = createServer();
}
@Override
@@ -86,6 +86,20 @@ public void tearDown() throws Exception {
super.tearDown();
}
+ /**
+ * Creates the {@linkAppServer}.This default implementation creates a server
+ * without SSLsupport listening for HTTP connections on a dynamically chosen
+ * port, which can be gotten once the server has been started via its
+ * {@link AppServer#getPort()} method. Subclasses may override if they need
+ * a more specialized server.
+ *
+ * @return the {@link AppServer}.
+ * @since 4.9
+ */
+ protected AppServer createServer() {
+ return new AppServer();
+ }
+
protected TestRepository<Repository> createTestRepository()
throws IOException {
return new TestRepository<>(createBareRepository());
@@ -165,4 +179,37 @@ public static String join(URIish base, String path) {
dir += "/";
return dir + path;
}
+
+ protected static String rewriteUrl(String url, String newProtocol,
+ int newPort) {
+ String newUrl = url;
+ if (newProtocol != null && !newProtocol.isEmpty()) {
+ int schemeEnd = newUrl.indexOf("://");
+ if (schemeEnd >= 0) {
+ newUrl = newProtocol + newUrl.substring(schemeEnd);
+ }
+ }
+ if (newPort > 0) {
+ newUrl = newUrl.replaceFirst(":\\d+/", ":" + newPort + "/");
+ } else {
+ // Remove the port, if any
+ newUrl = newUrl.replaceFirst(":\\d+/", "/");
+ }
+ return newUrl;
+ }
+
+ protected static URIish extendPath(URIish uri, String pathComponents)
+ throws URISyntaxException {
+ String raw = uri.toString();
+ String newComponents = pathComponents;
+ if (!newComponents.startsWith("/")) {
+ newComponents = '/' + newComponents;
+ }
+ if (!newComponents.endsWith("/")) {
+ newComponents += '/';
+ }
+ int i = raw.lastIndexOf('/');
+ raw = raw.substring(0, i) + newComponents + raw.substring(i + 1);
+ return new URIish(raw);
+ }
}
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java
index 9defcd9..03c0816 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/MockServletConfig.java
@@ -88,4 +88,4 @@ public String getServletName() {
public ServletContext getServletContext() {
return null;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java
index 605c69a..0ea0721 100644
--- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java
+++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/SimpleHttpServer.java
@@ -69,9 +69,15 @@ public class SimpleHttpServer {
private URIish uri;
+ private URIish secureUri;
+
public SimpleHttpServer(Repository repository) {
+ this(repository, false);
+ }
+
+ public SimpleHttpServer(Repository repository, boolean withSsl) {
this.db = repository;
- server = new AppServer();
+ server = new AppServer(0, withSsl ? 0 : -1);
}
public void start() throws Exception {
@@ -79,6 +85,10 @@ public void start() throws Exception {
server.setUp();
final String srcName = db.getDirectory().getName();
uri = toURIish(sBasic, srcName);
+ int sslPort = server.getSecurePort();
+ if (sslPort > 0) {
+ secureUri = uri.setPort(sslPort).setScheme("https");
+ }
}
public void stop() throws Exception {
@@ -89,6 +99,10 @@ public URIish getUri() {
return uri;
}
+ public URIish getSecureUri() {
+ return secureUri;
+ }
+
private ServletContextHandler smart(final String path) {
GitServlet gs = new GitServlet();
gs.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>() {
diff --git a/org.eclipse.jgit.junit/.settings/.api_filters b/org.eclipse.jgit.junit/.settings/.api_filters
deleted file mode 100644
index a70ce77..0000000
--- a/org.eclipse.jgit.junit/.settings/.api_filters
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<component id="org.eclipse.jgit.junit" version="2">
- <resource path="src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java" type="org.eclipse.jgit.junit.LocalDiskRepositoryTestCase">
- <filter comment="OK to use internal implementation in tests" id="643842064">
- <message_arguments>
- <message_argument value="FileRepository"/>
- <message_argument value="LocalDiskRepositoryTestCase"/>
- <message_argument value="createBareRepository()"/>
- </message_arguments>
- </filter>
- <filter comment="OK to use internal implementation in tests" id="643842064">
- <message_arguments>
- <message_argument value="FileRepository"/>
- <message_argument value="LocalDiskRepositoryTestCase"/>
- <message_argument value="createRepository(boolean, boolean)"/>
- </message_arguments>
- </filter>
- <filter comment="OK to use internal implementation in tests" id="643842064">
- <message_arguments>
- <message_argument value="FileRepository"/>
- <message_argument value="LocalDiskRepositoryTestCase"/>
- <message_argument value="createWorkRepository()"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/junit/RepositoryTestCase.java" type="org.eclipse.jgit.junit.RepositoryTestCase">
- <filter comment="OK to use internal implementation in tests" id="627060751">
- <message_arguments>
- <message_argument value="FileRepository"/>
- <message_argument value="RepositoryTestCase"/>
- <message_argument value="db"/>
- </message_arguments>
- </filter>
- </resource>
-</component>
diff --git a/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.junit/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.junit/.settings/org.eclipse.pde.api.tools.prefs
index cd148d9..c0030de 100644
--- a/org.eclipse.jgit.junit/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.junit/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,4 +1,4 @@
-#Tue Oct 18 00:52:01 CEST 2011
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -8,6 +8,10 @@
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -47,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -58,6 +63,7 @@
INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
INVALID_JAVADOC_TAG=Ignore
INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
LEAK_EXTEND=Warning
@@ -75,6 +81,7 @@
METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
@@ -83,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
index c864c5c..b3f92d9 100644
--- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
@@ -2,31 +2,31 @@
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit.junit
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-Localization: plugin
Bundle-Vendor: %provider_name
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.api;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.api.errors;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.dircache;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.errors;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.pack;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.merge;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.storage.file;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.treewalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util.io;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util.time;version="[4.8.1,4.9.0)",
+Import-Package: org.eclipse.jgit.api;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.api.errors;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.dircache;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.errors;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.merge;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.revwalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.storage.file;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.treewalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util.io;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util.time;version="[4.9.9,4.10.0)",
org.junit;version="[4.0.0,5.0.0)",
org.junit.rules;version="[4.9.0,5.0.0)",
org.junit.runner;version="[4.0.0,5.0.0)",
org.junit.runners.model;version="[4.5.0,5.0.0)"
-Export-Package: org.eclipse.jgit.junit;version="4.8.1";
+Export-Package: org.eclipse.jgit.junit;version="4.9.9";
uses:="org.eclipse.jgit.dircache,
org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
@@ -35,4 +35,4 @@
org.eclipse.jgit.util,
org.eclipse.jgit.storage.file,
org.eclipse.jgit.api",
- org.eclipse.jgit.junit.time;version="4.8.1"
+ org.eclipse.jgit.junit.time;version="4.9.9"
diff --git a/org.eclipse.jgit.junit/pom.xml b/org.eclipse.jgit.junit/pom.xml
index 3f843e6..16b9a6d 100644
--- a/org.eclipse.jgit.junit/pom.xml
+++ b/org.eclipse.jgit.junit/pom.xml
@@ -52,7 +52,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.junit</artifactId>
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
index 2962e71..5bf61f0 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
@@ -258,4 +258,27 @@ public static Path writeLink(Repository db, String link,
target);
}
+ /**
+ * Concatenate byte arrays.
+ *
+ * @param b
+ * byte arrays to combine together.
+ * @return a single byte array that contains all bytes copied from input
+ * byte arrays.
+ * @since 4.9
+ */
+ public static byte[] concat(byte[]... b) {
+ int n = 0;
+ for (byte[] a : b) {
+ n += a.length;
+ }
+
+ byte[] data = new byte[n];
+ n = 0;
+ for (byte[] a : b) {
+ System.arraycopy(a, 0, data, n, a.length);
+ n += a.length;
+ }
+ return data;
+ }
}
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java
index 22b5007..a3c869f 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java
@@ -50,4 +50,4 @@
@Target({ java.lang.annotation.ElementType.METHOD })
public @interface Repeat {
public abstract int n();
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
index 75e1a67..4230073 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
@@ -128,4 +128,4 @@ public Statement apply(Statement statement, Description description) {
}
return result;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/StrictWorkMonitor.java
similarity index 73%
copy from org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
copy to org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/StrictWorkMonitor.java
index 98a2a94..22b69a3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/StrictWorkMonitor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011, Google Inc.
+ * Copyright (C) 2017 Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -41,20 +41,38 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.eclipse.jgit.internal.storage.dfs;
+package org.eclipse.jgit.junit;
-import java.util.concurrent.atomic.AtomicLong;
+import static org.junit.Assert.assertEquals;
-final class DfsPackKey {
- final int hash;
+import org.eclipse.jgit.lib.ProgressMonitor;
- final AtomicLong cachedSize;
+public final class StrictWorkMonitor implements ProgressMonitor {
+ private int lastWork, totalWork;
- DfsPackKey() {
- // Multiply by 31 here so we can more directly combine with another
- // value without doing the multiply there.
- //
- hash = System.identityHashCode(this) * 31;
- cachedSize = new AtomicLong();
+ @Override
+ public void start(int totalTasks) {
+ // empty
+ }
+
+ @Override
+ public void beginTask(String title, int total) {
+ this.totalWork = total;
+ lastWork = 0;
+ }
+
+ @Override
+ public void update(int completed) {
+ lastWork += completed;
+ }
+
+ @Override
+ public void endTask() {
+ assertEquals("Units of work recorded", totalWork, lastWork);
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
}
}
diff --git a/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.pde.api.tools.prefs
index d585687..c0030de 100644
--- a/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.lfs.server.test/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,3 +1,4 @@
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -7,6 +8,10 @@
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -46,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -57,6 +63,7 @@
INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
INVALID_JAVADOC_TAG=Ignore
INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
LEAK_EXTEND=Warning
@@ -74,6 +81,7 @@
METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
@@ -82,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
index f7960c7..64a964a 100644
--- a/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit.lfs.server.test
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-Vendor: %provider_name
Bundle-Localization: plugin
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -27,11 +27,11 @@
org.eclipse.jetty.util.log;version="[9.4.5,10.0.0)",
org.eclipse.jetty.util.security;version="[9.4.5,10.0.0)",
org.eclipse.jetty.util.thread;version="[9.4.5,10.0.0)",
- org.eclipse.jgit.junit.http;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lfs.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lfs.server.fs;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lfs.test;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.junit.http;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lfs.test;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util;version="[4.9.9,4.10.0)",
org.hamcrest.core;version="[1.1.0,2.0.0)",
org.junit;version="[4.0.0,5.0.0)",
org.junit.runner;version="[4.0.0,5.0.0)",
diff --git a/org.eclipse.jgit.lfs.server.test/pom.xml b/org.eclipse.jgit.lfs.server.test/pom.xml
index 9fd7080..2fba3f8 100644
--- a/org.eclipse.jgit.lfs.server.test/pom.xml
+++ b/org.eclipse.jgit.lfs.server.test/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.lfs.server.test</artifactId>
diff --git a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/DownloadTest.java b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/DownloadTest.java
index f92e638..303d8056 100644
--- a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/DownloadTest.java
+++ b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/DownloadTest.java
@@ -81,7 +81,7 @@ public void testDownloadInvalidPathInfo()
fail("expected RuntimeException");
} catch (RuntimeException e) {
String error = String.format(
- "Invalid pathInfo '/%s' does not match '/{SHA-256}'", id);
+ "Invalid pathInfo: '/%s' does not match '/{SHA-256}'", id);
assertEquals(formatErrorMessage(SC_UNPROCESSABLE_ENTITY, error),
e.getMessage());
}
@@ -97,7 +97,7 @@ public void testDownloadInvalidId()
getContent(id, f);
fail("expected RuntimeException");
} catch (RuntimeException e) {
- String error = String.format("Invalid id: : %s", id);
+ String error = String.format("Invalid id: %s", id);
assertEquals(formatErrorMessage(SC_UNPROCESSABLE_ENTITY, error),
e.getMessage());
}
@@ -129,7 +129,7 @@ public void testLargeFileDownload() throws Exception {
long start = System.nanoTime();
long len = getContent(id, f2);
System.out.println(
- MessageFormat.format("dowloaded 10 MiB random data in {0}ms",
+ MessageFormat.format("downloaded 10 MiB random data in {0}ms",
(System.nanoTime() - start) / 1e6));
assertEquals(expectedLen, len);
FileUtils.delete(f.toFile(), FileUtils.RETRY);
@@ -138,7 +138,7 @@ public void testLargeFileDownload() throws Exception {
@SuppressWarnings("boxing")
private String formatErrorMessage(int status, String message) {
- return String.format("Status: %d {\n \"message\": \"%s\"\n}", status,
+ return String.format("Status: %d {\"message\":\"%s\"}", status,
message);
}
}
diff --git a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java
index e10660d..5da502e 100644
--- a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java
+++ b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java
@@ -265,4 +265,4 @@ protected long createPseudoRandomContentFile(Path f, long size)
}
return Files.size(f);
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.lfs.server/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.api.tools.prefs
index 3294d4f..112e3c3 100644
--- a/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.lfs.server/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,3 +1,4 @@
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -10,6 +11,7 @@
API_USE_SCAN_FIELD_SEVERITY=Error
API_USE_SCAN_METHOD_SEVERITY=Error
API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -49,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -87,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
index 3366e1f..62af450 100644
--- a/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
@@ -2,19 +2,19 @@
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit.lfs.server
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-Localization: plugin
Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs.server;version="4.8.1";
+Export-Package: org.eclipse.jgit.lfs.server;version="4.9.9";
uses:="javax.servlet.http,
org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.fs;version="4.8.1";
+ org.eclipse.jgit.lfs.server.fs;version="4.9.9";
uses:="javax.servlet,
javax.servlet.http,
org.eclipse.jgit.lfs.server,
org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.internal;version="4.8.1";x-internal:=true,
- org.eclipse.jgit.lfs.server.s3;version="4.8.1";
+ org.eclipse.jgit.lfs.server.internal;version="4.9.9";x-internal:=true,
+ org.eclipse.jgit.lfs.server.s3;version="4.9.9";
uses:="org.eclipse.jgit.lfs.server,
org.eclipse.jgit.lfs.lib"
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
@@ -24,14 +24,14 @@
javax.servlet.http;version="[3.1.0,4.0.0)",
org.apache.http;version="[4.3.0,5.0.0)",
org.apache.http.client;version="[4.3.0,5.0.0)",
- org.eclipse.jgit.annotations;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lfs.errors;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lfs.internal;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lfs.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport.http;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport.http.apache;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
+ org.eclipse.jgit.annotations;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lfs.errors;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lfs.internal;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.nls;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport.http;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport.http.apache;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util;version="[4.9.9,4.10.0)",
org.slf4j;version="[1.7.0,2.0.0)"
diff --git a/org.eclipse.jgit.lfs.server/pom.xml b/org.eclipse.jgit.lfs.server/pom.xml
index f247cae..e28e6d2 100644
--- a/org.eclipse.jgit.lfs.server/pom.xml
+++ b/org.eclipse.jgit.lfs.server/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.lfs.server</artifactId>
diff --git a/org.eclipse.jgit.lfs.server/resources/org/eclipse/jgit/lfs/server/internal/LfsServerText.properties b/org.eclipse.jgit.lfs.server/resources/org/eclipse/jgit/lfs/server/internal/LfsServerText.properties
index b2b487e..6597145 100644
--- a/org.eclipse.jgit.lfs.server/resources/org/eclipse/jgit/lfs/server/internal/LfsServerText.properties
+++ b/org.eclipse.jgit.lfs.server/resources/org/eclipse/jgit/lfs/server/internal/LfsServerText.properties
@@ -1,5 +1,5 @@
failedToCalcSignature=Failed to calculate a request signature: {0}
-invalidPathInfo=Invalid pathInfo ''{0}'' does not match ''/'{'SHA-256'}'''
+invalidPathInfo=Invalid pathInfo: ''{0}'' does not match ''/'{'SHA-256'}'''
objectNotFound=Object ''{0}'' not found
undefinedS3AccessKey=S3 configuration: 'accessKey' is undefined
undefinedS3Bucket=S3 configuration: 'bucket' is undefined
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java
index 86ca2d3..4fea92e 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/TransferHandler.java
@@ -164,4 +164,4 @@ private void addObjectInfo(Response.Body body, LfsObject o)
}
abstract Response.Body process() throws IOException;
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java
index 15c4448..d02d466 100644
--- a/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java
+++ b/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/fs/FileLfsServlet.java
@@ -216,10 +216,9 @@ protected static void sendError(HttpServletResponse rsp, int status, String mess
}
private static Gson createGson() {
- GsonBuilder gb = new GsonBuilder()
- .setFieldNamingPolicy(
- FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
- .setPrettyPrinting().disableHtmlEscaping();
- return gb.create();
+ return new GsonBuilder()
+ .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+ .disableHtmlEscaping()
+ .create();
}
}
diff --git a/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.lfs.test/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.lfs.test/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.lfs.test/.settings/org.eclipse.pde.api.tools.prefs
index d585687..c0030de 100644
--- a/org.eclipse.jgit.lfs.test/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.lfs.test/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,3 +1,4 @@
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -7,6 +8,10 @@
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -46,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -57,6 +63,7 @@
INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
INVALID_JAVADOC_TAG=Ignore
INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
LEAK_EXTEND=Warning
@@ -74,6 +81,7 @@
METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
@@ -82,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
index 2bdbb59..c18d99c 100644
--- a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
@@ -2,23 +2,23 @@
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit.lfs.test
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-Vendor: %provider_name
Bundle-Localization: plugin
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.internal.storage.dfs;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.junit;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lfs;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lfs.errors;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lfs.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.treewalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
+Import-Package: org.eclipse.jgit.internal.storage.dfs;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.junit;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lfs;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lfs.errors;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.revwalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.treewalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util;version="[4.9.9,4.10.0)",
org.hamcrest.core;version="[1.1.0,2.0.0)",
org.junit;version="[4.0.0,5.0.0)",
org.junit.runner;version="[4.0.0,5.0.0)",
org.junit.runners;version="[4.0.0,5.0.0)"
-Export-Package: org.eclipse.jgit.lfs.test;version="4.8.1";x-friends:="org.eclipse.jgit.lfs.server.test"
+Export-Package: org.eclipse.jgit.lfs.test;version="4.9.9";x-friends:="org.eclipse.jgit.lfs.server.test"
diff --git a/org.eclipse.jgit.lfs.test/pom.xml b/org.eclipse.jgit.lfs.test/pom.xml
index f599ca0..ced210c 100644
--- a/org.eclipse.jgit.lfs.test/pom.xml
+++ b/org.eclipse.jgit.lfs.test/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.lfs.test</artifactId>
diff --git a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java
index e754d6f..31ab783 100644
--- a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java
+++ b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LongObjectIdTest.java
@@ -291,6 +291,8 @@ public void testCompareTo() {
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
assertEquals(0, id1.compareTo(LongObjectId.fromString(
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")));
+ AnyLongObjectId self = id1;
+ assertEquals(0, id1.compareTo(self));
assertEquals(-1, id1.compareTo(LongObjectId.fromString(
"1123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")));
diff --git a/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.lfs/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.api.tools.prefs
index 3294d4f..112e3c3 100644
--- a/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.lfs/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,3 +1,4 @@
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -10,6 +11,7 @@
API_USE_SCAN_FIELD_SEVERITY=Error
API_USE_SCAN_METHOD_SEVERITY=Error
API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -49,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -87,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
index 489fd12..c71473f 100644
--- a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
@@ -2,20 +2,20 @@
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit.lfs
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-Localization: plugin
Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.lfs;version="4.8.1",
- org.eclipse.jgit.lfs.errors;version="4.8.1",
- org.eclipse.jgit.lfs.internal;version="4.8.1";x-friends:="org.eclipse.jgit.lfs.test,org.eclipse.jgit.lfs.server.fs,org.eclipse.jgit.lfs.server",
- org.eclipse.jgit.lfs.lib;version="4.8.1"
+Export-Package: org.eclipse.jgit.lfs;version="4.9.9",
+ org.eclipse.jgit.lfs.errors;version="4.9.9",
+ org.eclipse.jgit.lfs.internal;version="4.9.9";x-friends:="org.eclipse.jgit.lfs.test,org.eclipse.jgit.lfs.server.fs,org.eclipse.jgit.lfs.server",
+ org.eclipse.jgit.lfs.lib;version="4.9.9"
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.annotations;version="[4.8.1,4.9.0)";resolution:=optional,
- org.eclipse.jgit.attributes;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.errors;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.treewalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util;version="[4.8.1,4.9.0)"
+Import-Package: org.eclipse.jgit.annotations;version="[4.9.9,4.10.0)";resolution:=optional,
+ org.eclipse.jgit.attributes;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.errors;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.nls;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.treewalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util;version="[4.9.9,4.10.0)"
diff --git a/org.eclipse.jgit.lfs/pom.xml b/org.eclipse.jgit.lfs/pom.xml
index 98216fb..3c88796 100644
--- a/org.eclipse.jgit.lfs/pom.xml
+++ b/org.eclipse.jgit.lfs/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.lfs</artifactId>
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/InvalidLongObjectIdException.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/InvalidLongObjectIdException.java
index 1f6e2d1..44ac317 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/InvalidLongObjectIdException.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/InvalidLongObjectIdException.java
@@ -80,7 +80,7 @@ public InvalidLongObjectIdException(String idString) {
private static String asAscii(byte[] bytes, int offset, int length) {
try {
- return ": " + new String(bytes, offset, length, "US-ASCII"); //$NON-NLS-1$ //$NON-NLS-2$
+ return new String(bytes, offset, length, "US-ASCII"); //$NON-NLS-1$
} catch (UnsupportedEncodingException e2) {
return ""; //$NON-NLS-1$
} catch (StringIndexOutOfBoundsException e2) {
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java
index 867cca5..1598b9e 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/AtomicObjectOutputStream.java
@@ -146,4 +146,4 @@ public void abort() {
locked.unlock();
aborted = true;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
index 0d9c4b2..44c47da 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
@@ -2,7 +2,7 @@
<feature
id="org.eclipse.jgit"
label="%featureName"
- version="4.8.1.qualifier"
+ version="4.9.9.qualifier"
provider-name="%providerName">
<description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
index c6ae404..5071bee 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>jgit.tycho.parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
index c2662fc..62b3dd5 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
@@ -2,7 +2,7 @@
<feature
id="org.eclipse.jgit.http.apache"
label="%featureName"
- version="4.8.1.qualifier"
+ version="4.9.9.qualifier"
provider-name="%providerName">
<description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
index 4fa4fca..38f4aba 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>jgit.tycho.parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
index 408b1d8..1783966 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
@@ -2,7 +2,7 @@
<feature
id="org.eclipse.jgit.junit"
label="%featureName"
- version="4.8.1.qualifier"
+ version="4.9.9.qualifier"
provider-name="%providerName">
<description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
index bdaa809..c8a7e70 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>jgit.tycho.parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
index 7113e7b..232f9c6 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
@@ -2,7 +2,7 @@
<feature
id="org.eclipse.jgit.lfs"
label="%featureName"
- version="4.8.1.qualifier"
+ version="4.9.9.qualifier"
provider-name="%providerName">
<description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
index b39af8c..32cb40d 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>jgit.tycho.parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
index 1129615..8baafce 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
@@ -2,7 +2,7 @@
<feature
id="org.eclipse.jgit.pgm"
label="%featureName"
- version="4.8.1.qualifier"
+ version="4.9.9.qualifier"
provider-name="%providerName">
<description url="http://www.eclipse.org/jgit/">
@@ -31,8 +31,8 @@
version="0.0.0"/>
<requires>
- <import feature="org.eclipse.jgit" version="4.8.1" match="equivalent"/>
- <import feature="org.eclipse.jgit.lfs" version="4.8.1" match="equivalent"/>
+ <import feature="org.eclipse.jgit" version="4.9.9" match="equivalent"/>
+ <import feature="org.eclipse.jgit.lfs" version="4.9.9" match="equivalent"/>
</requires>
<plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
index b8cb2c8..62dd1c1 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>jgit.tycho.parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml
index 0556fb5..f0b3222 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml
@@ -2,7 +2,7 @@
<feature
id="org.eclipse.jgit.pgm.source"
label="%featureName"
- version="4.8.1.qualifier"
+ version="4.9.9.qualifier"
provider-name="%providerName">
<description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml
index 70813bc..a9fc6e5 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>jgit.tycho.parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
index 432c5d4..209a96c 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>jgit.tycho.parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.repository</artifactId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
index 5834a0c..6c74a88 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
@@ -2,7 +2,7 @@
<feature
id="org.eclipse.jgit.source"
label="%featureName"
- version="4.8.1.qualifier"
+ version="4.9.9.qualifier"
provider-name="%providerName">
<description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
index ec57876..b26350b 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>jgit.tycho.parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF
index a43734b..7190313 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF
@@ -2,4 +2,4 @@
Bundle-ManifestVersion: 2
Bundle-Name: JGit Target Platform Bundle
Bundle-SymbolicName: org.eclipse.jgit.target
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target
index 8051080..b4b3908 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.5.target
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?pde?>
<!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.5" sequenceNumber="1502749391">
+<target name="jgit-4.5" sequenceNumber="1504052010">
<locations>
<location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
<unit id="org.eclipse.jetty.client" version="9.4.5.v20170502"/>
@@ -63,6 +63,8 @@
<location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
<unit id="org.apache.ant" version="1.9.6.v201510161327"/>
<unit id="org.apache.ant.source" version="1.9.6.v201510161327"/>
+ <unit id="org.apache.commons.codec" version="1.9.0.v20170208-1614"/>
+ <unit id="org.apache.commons.codec.source" version="1.9.0.v20170208-1614"/>
<unit id="org.apache.commons.compress" version="1.6.0.v201310281400"/>
<unit id="org.apache.commons.compress.source" version="1.6.0.v201310281400"/>
<unit id="org.apache.commons.logging" version="1.1.1.v201101211721"/>
@@ -73,8 +75,8 @@
<unit id="org.apache.httpcomponents.httpclient.source" version="4.3.6.v201511171540"/>
<unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
<unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/>
- <unit id="org.kohsuke.args4j" version="2.0.21.v201301150030"/>
- <unit id="org.kohsuke.args4j.source" version="2.0.21.v201301150030"/>
+ <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/>
+ <unit id="org.kohsuke.args4j.source" version="2.33.0.v20160323-2218"/>
<unit id="org.hamcrest.core" version="1.3.0.v201303031735"/>
<unit id="org.hamcrest.core.source" version="1.3.0.v201303031735"/>
<unit id="org.hamcrest.library" version="1.3.0.v201505072020"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target
index b6bbcda..48cd300 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?pde?>
<!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.6" sequenceNumber="1502749371">
+<target name="jgit-4.6" sequenceNumber="1504051999">
<locations>
<location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
<unit id="org.eclipse.jetty.client" version="9.4.5.v20170502"/>
@@ -25,6 +25,8 @@
<location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
<unit id="org.apache.ant" version="1.9.6.v201510161327"/>
<unit id="org.apache.ant.source" version="1.9.6.v201510161327"/>
+ <unit id="org.apache.commons.codec" version="1.9.0.v20170208-1614"/>
+ <unit id="org.apache.commons.codec.source" version="1.9.0.v20170208-1614"/>
<unit id="org.apache.commons.compress" version="1.6.0.v201310281400"/>
<unit id="org.apache.commons.compress.source" version="1.6.0.v201310281400"/>
<unit id="org.apache.commons.logging" version="1.1.1.v201101211721"/>
@@ -35,8 +37,8 @@
<unit id="org.apache.httpcomponents.httpclient.source" version="4.3.6.v201511171540"/>
<unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
<unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/>
- <unit id="org.kohsuke.args4j" version="2.0.21.v201301150030"/>
- <unit id="org.kohsuke.args4j.source" version="2.0.21.v201301150030"/>
+ <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/>
+ <unit id="org.kohsuke.args4j.source" version="2.33.0.v20160323-2218"/>
<unit id="org.hamcrest.core" version="1.3.0.v201303031735"/>
<unit id="org.hamcrest.core.source" version="1.3.0.v201303031735"/>
<unit id="org.hamcrest.library" version="1.3.0.v201505072020"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target
index 6071c8f..4ac65dd 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?pde?>
<!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.7" sequenceNumber="1502749365">
+<target name="jgit-4.7" sequenceNumber="1504051975">
<locations>
<location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
<unit id="org.eclipse.jetty.client" version="9.4.5.v20170502"/>
@@ -25,6 +25,8 @@
<location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
<unit id="org.apache.ant" version="1.9.6.v201510161327"/>
<unit id="org.apache.ant.source" version="1.9.6.v201510161327"/>
+ <unit id="org.apache.commons.codec" version="1.9.0.v20170208-1614"/>
+ <unit id="org.apache.commons.codec.source" version="1.9.0.v20170208-1614"/>
<unit id="org.apache.commons.compress" version="1.6.0.v201310281400"/>
<unit id="org.apache.commons.compress.source" version="1.6.0.v201310281400"/>
<unit id="org.apache.commons.logging" version="1.1.1.v201101211721"/>
@@ -35,8 +37,8 @@
<unit id="org.apache.httpcomponents.httpclient.source" version="4.3.6.v201511171540"/>
<unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
<unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/>
- <unit id="org.kohsuke.args4j" version="2.0.21.v201301150030"/>
- <unit id="org.kohsuke.args4j.source" version="2.0.21.v201301150030"/>
+ <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/>
+ <unit id="org.kohsuke.args4j.source" version="2.33.0.v20160323-2218"/>
<unit id="org.hamcrest.core" version="1.3.0.v201303031735"/>
<unit id="org.hamcrest.core.source" version="1.3.0.v201303031735"/>
<unit id="org.hamcrest.library" version="1.3.0.v201505072020"/>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target
new file mode 100644
index 0000000..d82fe3e
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?pde?>
+<!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
+<target name="jgit-4.8" sequenceNumber="1535021913">
+ <locations>
+ <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+ <unit id="org.eclipse.jetty.client" version="9.4.5.v20170502"/>
+ <unit id="org.eclipse.jetty.client.source" version="9.4.5.v20170502"/>
+ <unit id="org.eclipse.jetty.continuation" version="9.4.5.v20170502"/>
+ <unit id="org.eclipse.jetty.continuation.source" version="9.4.5.v20170502"/>
+ <unit id="org.eclipse.jetty.http" version="9.4.5.v20170502"/>
+ <unit id="org.eclipse.jetty.http.source" version="9.4.5.v20170502"/>
+ <unit id="org.eclipse.jetty.io" version="9.4.5.v20170502"/>
+ <unit id="org.eclipse.jetty.io.source" version="9.4.5.v20170502"/>
+ <unit id="org.eclipse.jetty.security" version="9.4.5.v20170502"/>
+ <unit id="org.eclipse.jetty.security.source" version="9.4.5.v20170502"/>
+ <unit id="org.eclipse.jetty.server" version="9.4.5.v20170502"/>
+ <unit id="org.eclipse.jetty.server.source" version="9.4.5.v20170502"/>
+ <unit id="org.eclipse.jetty.servlet" version="9.4.5.v20170502"/>
+ <unit id="org.eclipse.jetty.servlet.source" version="9.4.5.v20170502"/>
+ <unit id="org.eclipse.jetty.util" version="9.4.5.v20170502"/>
+ <unit id="org.eclipse.jetty.util.source" version="9.4.5.v20170502"/>
+ <repository id="jetty-9.4.5" location="http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.5.v20170502/"/>
+ </location>
+ <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+ <unit id="org.apache.ant" version="1.9.6.v201510161327"/>
+ <unit id="org.apache.ant.source" version="1.9.6.v201510161327"/>
+ <unit id="org.apache.commons.codec" version="1.9.0.v20170208-1614"/>
+ <unit id="org.apache.commons.codec.source" version="1.9.0.v20170208-1614"/>
+ <unit id="org.apache.commons.compress" version="1.6.0.v201310281400"/>
+ <unit id="org.apache.commons.compress.source" version="1.6.0.v201310281400"/>
+ <unit id="org.apache.commons.logging" version="1.1.1.v201101211721"/>
+ <unit id="org.apache.commons.logging.source" version="1.1.1.v201101211721"/>
+ <unit id="org.apache.httpcomponents.httpcore" version="4.3.3.v201411290715"/>
+ <unit id="org.apache.httpcomponents.httpcore.source" version="4.3.3.v201411290715"/>
+ <unit id="org.apache.httpcomponents.httpclient" version="4.3.6.v201511171540"/>
+ <unit id="org.apache.httpcomponents.httpclient.source" version="4.3.6.v201511171540"/>
+ <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
+ <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/>
+ <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/>
+ <unit id="org.kohsuke.args4j.source" version="2.33.0.v20160323-2218"/>
+ <unit id="org.hamcrest.core" version="1.3.0.v20180420-1519"/>
+ <unit id="org.hamcrest.core.source" version="1.3.0.v20180420-1519"/>
+ <unit id="org.hamcrest.library" version="1.3.0.v20180524-2246"/>
+ <unit id="org.hamcrest.library.source" version="1.3.0.v20180524-2246"/>
+ <unit id="javaewah" version="1.1.6.v20160919-1400"/>
+ <unit id="javaewah.source" version="1.1.6.v20160919-1400"/>
+ <unit id="org.objenesis" version="1.0.0.v201505121915"/>
+ <unit id="org.objenesis.source" version="1.0.0.v201505121915"/>
+ <unit id="org.mockito" version="1.8.4.v201303031500"/>
+ <unit id="org.mockito.source" version="1.8.4.v201303031500"/>
+ <unit id="com.google.gson" version="2.2.4.v201311231704"/>
+ <unit id="com.jcraft.jsch" version="0.1.54.v20170116-1932"/>
+ <unit id="com.jcraft.jsch.source" version="0.1.54.v20170116-1932"/>
+ <unit id="org.junit" version="4.12.0.v201504281640"/>
+ <unit id="org.junit.source" version="4.12.0.v201504281640"/>
+ <unit id="javax.servlet" version="3.1.0.v201410161800"/>
+ <unit id="javax.servlet.source" version="3.1.0.v201410161800"/>
+ <unit id="org.tukaani.xz" version="1.3.0.v201308270617"/>
+ <unit id="org.tukaani.xz.source" version="1.3.0.v201308270617"/>
+ <unit id="org.slf4j.api" version="1.7.2.v20121108-1250"/>
+ <unit id="org.slf4j.api.source" version="1.7.2.v20121108-1250"/>
+ <unit id="org.slf4j.impl.log4j12" version="1.7.2.v20131105-2200"/>
+ <unit id="org.slf4j.impl.log4j12.source" version="1.7.2.v20131105-2200"/>
+ <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20180606145124/repository"/>
+ </location>
+ <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+ <unit id="org.eclipse.osgi" version="0.0.0"/>
+ <repository location="http://download.eclipse.org/releases/photon/"/>
+ </location>
+ </locations>
+</target>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd
new file mode 100644
index 0000000..cd1954f
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd
@@ -0,0 +1,8 @@
+target "jgit-4.8" with source configurePhase
+
+include "projects/jetty-9.4.5.tpd"
+include "orbit/R20180606145124-Photon.tpd"
+
+location "http://download.eclipse.org/releases/photon/" {
+ org.eclipse.osgi lazy
+}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20170516192513-Oxygen.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20170516192513-Oxygen.tpd
index ef19fa6..f4cb572 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20170516192513-Oxygen.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20170516192513-Oxygen.tpd
@@ -4,6 +4,8 @@
location "http://download.eclipse.org/tools/orbit/downloads/drops/R20170516192513/repository" {
org.apache.ant [1.9.6.v201510161327,1.9.6.v201510161327]
org.apache.ant.source [1.9.6.v201510161327,1.9.6.v201510161327]
+ org.apache.commons.codec [1.9.0.v20170208-1614,1.9.0.v20170208-1614]
+ org.apache.commons.codec.source [1.9.0.v20170208-1614,1.9.0.v20170208-1614]
org.apache.commons.compress [1.6.0.v201310281400,1.6.0.v201310281400]
org.apache.commons.compress.source [1.6.0.v201310281400,1.6.0.v201310281400]
org.apache.commons.logging [1.1.1.v201101211721,1.1.1.v201101211721]
@@ -14,8 +16,8 @@
org.apache.httpcomponents.httpclient.source [4.3.6.v201511171540,4.3.6.v201511171540]
org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815]
org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815]
- org.kohsuke.args4j [2.0.21.v201301150030,2.0.21.v201301150030]
- org.kohsuke.args4j.source [2.0.21.v201301150030,2.0.21.v201301150030]
+ org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
+ org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
org.hamcrest.core [1.3.0.v201303031735,1.3.0.v201303031735]
org.hamcrest.core.source [1.3.0.v201303031735,1.3.0.v201303031735]
org.hamcrest.library [1.3.0.v201505072020,1.3.0.v201505072020]
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180606145124-Photon.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180606145124-Photon.tpd
new file mode 100644
index 0000000..a7ad1a4
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180606145124-Photon.tpd
@@ -0,0 +1,44 @@
+target "R20180606145124-Photon" with source configurePhase
+// see http://download.eclipse.org/tools/orbit/downloads/
+
+location "http://download.eclipse.org/tools/orbit/downloads/drops/R20180606145124/repository" {
+ org.apache.ant [1.9.6.v201510161327,1.9.6.v201510161327]
+ org.apache.ant.source [1.9.6.v201510161327,1.9.6.v201510161327]
+ org.apache.commons.codec [1.9.0.v20170208-1614,1.9.0.v20170208-1614]
+ org.apache.commons.codec.source [1.9.0.v20170208-1614,1.9.0.v20170208-1614]
+ org.apache.commons.compress [1.6.0.v201310281400,1.6.0.v201310281400]
+ org.apache.commons.compress.source [1.6.0.v201310281400,1.6.0.v201310281400]
+ org.apache.commons.logging [1.1.1.v201101211721,1.1.1.v201101211721]
+ org.apache.commons.logging.source [1.1.1.v201101211721,1.1.1.v201101211721]
+ org.apache.httpcomponents.httpcore [4.3.3.v201411290715,4.3.3.v201411290715]
+ org.apache.httpcomponents.httpcore.source [4.3.3.v201411290715,4.3.3.v201411290715]
+ org.apache.httpcomponents.httpclient [4.3.6.v201511171540,4.3.6.v201511171540]
+ org.apache.httpcomponents.httpclient.source [4.3.6.v201511171540,4.3.6.v201511171540]
+ org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815]
+ org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815]
+ org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
+ org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
+ org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
+ org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
+ org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
+ org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
+ javaewah [1.1.6.v20160919-1400,1.1.6.v20160919-1400]
+ javaewah.source [1.1.6.v20160919-1400,1.1.6.v20160919-1400]
+ org.objenesis [1.0.0.v201505121915,1.0.0.v201505121915]
+ org.objenesis.source [1.0.0.v201505121915,1.0.0.v201505121915]
+ org.mockito [1.8.4.v201303031500,1.8.4.v201303031500]
+ org.mockito.source [1.8.4.v201303031500,1.8.4.v201303031500]
+ com.google.gson [2.2.4.v201311231704,2.2.4.v201311231704]
+ com.jcraft.jsch [0.1.54.v20170116-1932,0.1.54.v20170116-1932]
+ com.jcraft.jsch.source [0.1.54.v20170116-1932,0.1.54.v20170116-1932]
+ org.junit [4.12.0.v201504281640,4.12.0.v201504281640]
+ org.junit.source [4.12.0.v201504281640,4.12.0.v201504281640]
+ javax.servlet [3.1.0.v201410161800,3.1.0.v201410161800]
+ javax.servlet.source [3.1.0.v201410161800,3.1.0.v201410161800]
+ org.tukaani.xz [1.3.0.v201308270617,1.3.0.v201308270617]
+ org.tukaani.xz.source [1.3.0.v201308270617,1.3.0.v201308270617]
+ org.slf4j.api [1.7.2.v20121108-1250,1.7.2.v20121108-1250]
+ org.slf4j.api.source [1.7.2.v20121108-1250,1.7.2.v20121108-1250]
+ org.slf4j.impl.log4j12 [1.7.2.v20131105-2200,1.7.2.v20131105-2200]
+ org.slf4j.impl.log4j12.source [1.7.2.v20131105-2200,1.7.2.v20131105-2200]
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml
index d1934e7..69ff043 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml
@@ -49,7 +49,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>jgit.tycho.parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.target</artifactId>
diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml
index 2613686..8c04633 100644
--- a/org.eclipse.jgit.packaging/pom.xml
+++ b/org.eclipse.jgit.packaging/pom.xml
@@ -53,7 +53,7 @@
<groupId>org.eclipse.jgit</groupId>
<artifactId>jgit.tycho.parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
<packaging>pom</packaging>
<name>JGit Tycho Parent</name>
diff --git a/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.pgm.test/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.pgm.test/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.pgm.test/.settings/org.eclipse.pde.api.tools.prefs
index cd148d9..c0030de 100644
--- a/org.eclipse.jgit.pgm.test/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.pgm.test/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,4 +1,4 @@
-#Tue Oct 18 00:52:01 CEST 2011
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -8,6 +8,10 @@
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -47,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -58,6 +63,7 @@
INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
INVALID_JAVADOC_TAG=Ignore
INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
LEAK_EXTEND=Warning
@@ -75,6 +81,7 @@
METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
@@ -83,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.pgm.test/BUILD b/org.eclipse.jgit.pgm.test/BUILD
index 5d4a175..5bedf9a 100644
--- a/org.eclipse.jgit.pgm.test/BUILD
+++ b/org.eclipse.jgit.pgm.test/BUILD
@@ -13,6 +13,7 @@
tags = ["pgm"],
deps = [
":helpers",
+ "//lib:args4j",
"//lib:commons-compress",
"//lib:javaewah",
"//lib:junit",
diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
index 37bef75..f48032c 100644
--- a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
@@ -2,30 +2,30 @@
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit.pgm.test
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-Vendor: %provider_name
Bundle-Localization: plugin
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Import-Package: org.eclipse.jgit.api;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.api.errors;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.diff;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.dircache;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.file;version="4.8.1",
- org.eclipse.jgit.junit;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.merge;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.pgm;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.pgm.internal;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.pgm.opt;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.storage.file;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.treewalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util.io;version="[4.8.1,4.9.0)",
+Import-Package: org.eclipse.jgit.api;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.api.errors;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.diff;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.dircache;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.file;version="4.9.9",
+ org.eclipse.jgit.junit;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.merge;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.pgm;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.pgm.internal;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.pgm.opt;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.revwalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.storage.file;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.treewalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util.io;version="[4.9.9,4.10.0)",
org.hamcrest.core;bundle-version="[1.1.0,2.0.0)",
org.junit;version="[4.11.0,5.0.0)",
org.junit.rules;version="[4.11.0,5.0.0)",
- org.kohsuke.args4j;version="[2.0.12,2.1.0)"
+ org.kohsuke.args4j;version="[2.33.0,3.0.0)"
Require-Bundle: org.tukaani.xz;bundle-version="[1.3.0,2.0.0)"
diff --git a/org.eclipse.jgit.pgm.test/pom.xml b/org.eclipse.jgit.pgm.test/pom.xml
index ee234d8..d180157 100644
--- a/org.eclipse.jgit.pgm.test/pom.xml
+++ b/org.eclipse.jgit.pgm.test/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.pgm.test</artifactId>
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
index 4b86b60..a98dd1c 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
@@ -71,6 +71,23 @@
import org.junit.Test;
public class CheckoutTest extends CLIRepositoryTestCase {
+ /**
+ * Executes specified git command (with arguments), captures exception and
+ * returns its message as an array of lines. Throws an AssertionError if no
+ * exception is thrown.
+ *
+ * @param command
+ * a valid git command line, e.g. "git branch -h"
+ * @return message contained within the exception
+ */
+ private String[] executeExpectingException(String command) {
+ try {
+ execute(command);
+ throw new AssertionError("Expected Die");
+ } catch (Exception e) {
+ return e.getMessage().split(System.lineSeparator());
+ }
+ }
@Test
public void testCheckoutSelf() throws Exception {
@@ -107,7 +124,7 @@ public void testCheckoutNewBranch() throws Exception {
public void testCheckoutNonExistingBranch() throws Exception {
assertStringArrayEquals(
"error: pathspec 'side' did not match any file(s) known to git.",
- execute("git checkout side"));
+ executeExpectingException("git checkout side"));
}
@Test
@@ -131,7 +148,7 @@ public void testCheckoutNewBranchOnBranchToBeBorn() throws Exception {
public void testCheckoutUnresolvedHead() throws Exception {
assertStringArrayEquals(
"error: pathspec 'HEAD' did not match any file(s) known to git.",
- execute("git checkout HEAD"));
+ executeExpectingException("git checkout HEAD"));
}
@Test
@@ -159,7 +176,8 @@ public void testCheckoutExistingBranchWithConflict() throws Exception {
writeTrashFile("a", "New Hello world a");
git.add().addFilepattern(".").call();
- String[] execute = execute("git checkout branch_1");
+ String[] execute = executeExpectingException(
+ "git checkout branch_1");
assertEquals(
"error: Your local changes to the following files would be overwritten by checkout:",
execute[0]);
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java
index 086e72e..e97762d 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java
@@ -46,6 +46,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.util.Arrays;
@@ -71,6 +72,12 @@ private void initialCommitAndTag() throws Exception {
git.tag().setName("v1.0").call();
}
+ private void secondCommit() throws Exception {
+ writeTrashFile("greeting", "Hello, world!");
+ git.add().addFilepattern("greeting").call();
+ git.commit().setMessage("2nd commit").call();
+ }
+
@Test
public void testNoHead() throws Exception {
assertEquals(CLIText.fatalError(CLIText.get().noNamesFound),
@@ -94,9 +101,7 @@ public void testDescribeTag() throws Exception {
@Test
public void testDescribeCommit() throws Exception {
initialCommitAndTag();
- writeTrashFile("greeting", "Hello, world!");
- git.add().addFilepattern("greeting").call();
- git.commit().setMessage("2nd commit").call();
+ secondCommit();
assertArrayEquals(new String[] { "v1.0-1-g56f6ceb", "" },
execute("git describe"));
}
@@ -109,6 +114,47 @@ public void testDescribeTagLong() throws Exception {
}
@Test
+ public void testDescribeCommitMatch() throws Exception {
+ initialCommitAndTag();
+ secondCommit();
+ assertArrayEquals(new String[] { "v1.0-1-g56f6ceb", "" },
+ execute("git describe --match v1.*"));
+ }
+
+ @Test
+ public void testDescribeCommitMatch2() throws Exception {
+ initialCommitAndTag();
+ secondCommit();
+ git.tag().setName("v2.0").call();
+ assertArrayEquals(new String[] { "v1.0-1-g56f6ceb", "" },
+ execute("git describe --match v1.*"));
+ }
+
+ @Test
+ public void testDescribeCommitMultiMatch() throws Exception {
+ initialCommitAndTag();
+ secondCommit();
+ git.tag().setName("v2.0.0").call();
+ git.tag().setName("v2.1.1").call();
+ assertArrayEquals("git yields v2.0.0", new String[] { "v2.0.0", "" },
+ execute("git describe --match v2.0* --match v2.1.*"));
+ }
+
+ @Test
+ public void testDescribeCommitNoMatch() throws Exception {
+ initialCommitAndTag();
+ writeTrashFile("greeting", "Hello, world!");
+ secondCommit();
+ try {
+ execute("git describe --match 1.*");
+ fail("git describe should not find any tag matching 1.*");
+ } catch (Die e) {
+ assertEquals("No names found, cannot describe anything.",
+ e.getMessage());
+ }
+ }
+
+ @Test
public void testHelpArgumentBeforeUnknown() throws Exception {
String[] output = execute("git describe -h -XYZ");
String all = Arrays.toString(output);
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ReflogTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ReflogTest.java
index 7330ee9..bf6bacb 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ReflogTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ReflogTest.java
@@ -80,4 +80,4 @@ public void testBranch() throws Exception {
"" }, execute("git reflog refs/heads/side"));
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java
index 44a7630..81287c1 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java
@@ -69,7 +69,7 @@ public void setUp() throws Exception {
public void testPathOptionHelp() throws Exception {
String[] result = execute("git reset -h");
assertTrue("Unexpected argument: " + result[1],
- result[1].endsWith("[-- path ... ...]"));
+ result[1].endsWith("[-- path ...]"));
}
@Test
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RmTest.java
similarity index 62%
copy from org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
copy to org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RmTest.java
index 98a2a94..00a1a9a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RmTest.java
@@ -1,6 +1,5 @@
/*
- * Copyright (C) 2011, Google Inc.
- * and other copyright owners as documented in the project's IP log.
+ * Copyright (C) 2013 Robin Stocker <robin@nibor.org> and others.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
@@ -40,21 +39,42 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+package org.eclipse.jgit.pgm;
-package org.eclipse.jgit.internal.storage.dfs;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
-import java.util.concurrent.atomic.AtomicLong;
+import java.io.File;
-final class DfsPackKey {
- final int hash;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.junit.Before;
+import org.junit.Test;
- final AtomicLong cachedSize;
+public class RmTest extends CLIRepositoryTestCase {
+ private Git git;
- DfsPackKey() {
- // Multiply by 31 here so we can more directly combine with another
- // value without doing the multiply there.
- //
- hash = System.identityHashCode(this) * 31;
- cachedSize = new AtomicLong();
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ git = new Git(db);
+ }
+
+ @Test
+ public void multiplePathsShouldBeRemoved() throws Exception {
+ File a = writeTrashFile("a", "Hello");
+ File b = writeTrashFile("b", "world!");
+ git.add().addFilepattern("a").addFilepattern("b").call();
+
+ String[] result = execute("git rm a b");
+ assertArrayEquals(new String[] { "" }, result);
+ DirCache cache = db.readDirCache();
+ assertNull(cache.getEntry("a"));
+ assertNull(cache.getEntry("b"));
+ assertFalse(a.exists());
+ assertFalse(b.exists());
}
}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java
index 368047c..cc68da2 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java
@@ -60,7 +60,7 @@ public class StatusTest extends CLIRepositoryTestCase {
public void testPathOptionHelp() throws Exception {
String[] result = execute("git status -h");
assertTrue("Unexpected argument: " + result[1],
- result[1].endsWith("[-- path ... ...]"));
+ result[1].endsWith("[-- path ...]"));
}
@Test
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/TextBuiltinTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/TextBuiltinTest.java
new file mode 100644
index 0000000..256d2af
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/TextBuiltinTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017, Ned Twigg <ned.twigg@diffplug.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.pgm;
+
+import static org.eclipse.jgit.junit.JGitTestUtil.check;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.eclipse.jgit.pgm.opt.CmdLineParser;
+import org.eclipse.jgit.pgm.opt.SubcommandHandler;
+import org.junit.Test;
+import org.kohsuke.args4j.Argument;
+
+public class TextBuiltinTest extends CLIRepositoryTestCase {
+ public static class GitCliJGitWrapperParser {
+ @Argument(index = 0, metaVar = "metaVar_command", required = true, handler = SubcommandHandler.class)
+ TextBuiltin subcommand;
+
+ @Argument(index = 1, metaVar = "metaVar_arg")
+ List<String> arguments = new ArrayList<>();
+ }
+
+ private String[] runAndCaptureUsingInitRaw(String... args)
+ throws Exception {
+ CLIGitCommand.Result result = new CLIGitCommand.Result();
+
+ GitCliJGitWrapperParser bean = new GitCliJGitWrapperParser();
+ final CmdLineParser clp = new CmdLineParser(bean);
+ clp.parseArgument(args);
+
+ final TextBuiltin cmd = bean.subcommand;
+ cmd.initRaw(db, null, null, result.out, result.err);
+ cmd.execute(bean.arguments.toArray(new String[bean.arguments.size()]));
+ if (cmd.getOutputWriter() != null) {
+ cmd.getOutputWriter().flush();
+ }
+ if (cmd.getErrorWriter() != null) {
+ cmd.getErrorWriter().flush();
+ }
+ return result.outLines().toArray(new String[0]);
+ }
+
+ @Test
+ public void testCleanDeleteDirs() throws Exception {
+ try (Git git = new Git(db)) {
+ git.commit().setMessage("initial commit").call();
+
+ writeTrashFile("dir/file", "someData");
+ writeTrashFile("a", "someData");
+ writeTrashFile("b", "someData");
+
+ // all these files should be there
+ assertTrue(check(db, "a"));
+ assertTrue(check(db, "b"));
+ assertTrue(check(db, "dir/file"));
+
+ assertArrayOfLinesEquals(new String[] { "Removing a", "Removing b",
+ "Removing dir/" },
+ runAndCaptureUsingInitRaw("clean", "-d", "-f"));
+ assertFalse(check(db, "a"));
+ assertFalse(check(db, "b"));
+ assertFalse(check(db, "dir/file"));
+ }
+ }
+}
diff --git a/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.pgm/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.pgm/.settings/org.eclipse.pde.api.tools.prefs
index cd148d9..c0030de 100644
--- a/org.eclipse.jgit.pgm/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.pgm/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,4 +1,4 @@
-#Tue Oct 18 00:52:01 CEST 2011
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -8,6 +8,10 @@
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -47,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -58,6 +63,7 @@
INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
INVALID_JAVADOC_TAG=Ignore
INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
LEAK_EXTEND=Warning
@@ -75,6 +81,7 @@
METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
@@ -83,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index 5bde6a3..aad2b51 100644
--- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit.pgm
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-Vendor: %provider_name
Bundle-ActivationPolicy: lazy
Bundle-Localization: plugin
@@ -27,46 +27,49 @@
org.eclipse.jetty.util.log;version="[9.4.5,10.0.0)",
org.eclipse.jetty.util.security;version="[9.4.5,10.0.0)",
org.eclipse.jetty.util.thread;version="[9.4.5,10.0.0)",
- org.eclipse.jgit.api;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.api.errors;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.archive;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.awtui;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.blame;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.diff;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.dircache;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.errors;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.gitrepo;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.ketch;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.pack;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lfs;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lfs.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lfs.server;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lfs.server.fs;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lfs.server.s3;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.merge;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.notes;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.revplot;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.revwalk.filter;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.storage.file;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.storage.pack;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport.http.apache;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport.resolver;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.treewalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util.io;version="[4.8.1,4.9.0)",
- org.kohsuke.args4j;version="[2.0.12,2.1.0)",
- org.kohsuke.args4j.spi;version="[2.0.15,2.1.0)"
-Export-Package: org.eclipse.jgit.console;version="4.8.1";
+ org.eclipse.jgit.api;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.api.errors;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.archive;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.awtui;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.blame;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.diff;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.dircache;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.errors;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.gitrepo;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.ketch;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.io;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lfs;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lfs.server;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lfs.server.s3;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.merge;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.nls;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.notes;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.revplot;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.revwalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.revwalk.filter;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.storage.file;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.storage.pack;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport.http.apache;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.treewalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util.io;version="[4.9.9,4.10.0)",
+ org.kohsuke.args4j;version="[2.33.0,3.0.0)",
+ org.kohsuke.args4j.spi;version="[2.33.0,3.0.0)"
+Export-Package: org.eclipse.jgit.console;version="4.9.9";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.util",
- org.eclipse.jgit.pgm;version="4.8.1";
+ org.eclipse.jgit.pgm;version="4.9.9";
uses:="org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.pgm.opt,
@@ -77,11 +80,11 @@
org.eclipse.jgit.treewalk,
javax.swing,
org.eclipse.jgit.transport",
- org.eclipse.jgit.pgm.debug;version="4.8.1";
+ org.eclipse.jgit.pgm.debug;version="4.9.9";
uses:="org.eclipse.jgit.util.io,
org.eclipse.jgit.pgm",
- org.eclipse.jgit.pgm.internal;version="4.8.1";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
- org.eclipse.jgit.pgm.opt;version="4.8.1";
+ org.eclipse.jgit.pgm.internal;version="4.9.9";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test",
+ org.eclipse.jgit.pgm.opt;version="4.9.9";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.kohsuke.args4j.spi,
diff --git a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
index d5d71ee..06a9af4 100644
--- a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
Bundle-Name: org.eclipse.jgit.pgm - Sources
Bundle-SymbolicName: org.eclipse.jgit.pgm.source
Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 4.8.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="4.8.1.qualifier";roots="."
+Bundle-Version: 4.9.9.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="4.9.9.qualifier";roots="."
diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
index 5495be6..9025473 100644
--- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
+++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
@@ -38,10 +38,12 @@
org.eclipse.jgit.pgm.UploadPack
org.eclipse.jgit.pgm.Version
+org.eclipse.jgit.pgm.debug.BenchmarkReftable
org.eclipse.jgit.pgm.debug.DiffAlgorithms
org.eclipse.jgit.pgm.debug.LfsStore
org.eclipse.jgit.pgm.debug.MakeCacheTree
org.eclipse.jgit.pgm.debug.ReadDirCache
+org.eclipse.jgit.pgm.debug.ReadReftable
org.eclipse.jgit.pgm.debug.RebuildCommitGraph
org.eclipse.jgit.pgm.debug.RebuildRefTree
org.eclipse.jgit.pgm.debug.ShowCacheTree
@@ -49,5 +51,6 @@
org.eclipse.jgit.pgm.debug.ShowDirCache
org.eclipse.jgit.pgm.debug.ShowPackDelta
org.eclipse.jgit.pgm.debug.TextHashFunctions
-org.eclipse.jgit.pgm.debug.WriteDirCache
-
+org.eclipse.jgit.pgm.debug.VerifyReftable
+org.eclipse.jgit.pgm.debug.WriteReftable
+org.eclipse.jgit.pgm.debug.WriteReftable
diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml
index 7d6ec1d..ea3424d 100644
--- a/org.eclipse.jgit.pgm/pom.xml
+++ b/org.eclipse.jgit.pgm/pom.xml
@@ -50,7 +50,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.pgm</artifactId>
diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
index c3d7c68..cb0ea1b 100644
--- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
+++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
@@ -75,6 +75,7 @@
initializedEmptyGitRepositoryIn=Initialized empty Git repository in {0}
invalidHttpProxyOnlyHttpSupported=Invalid http_proxy: {0}: Only http supported.
invalidRecurseSubmodulesMode=Invalid recurse submodules mode: {0}
+invalidUntrackedFilesMode=Invalid untracked files mode ''{0}''
jgitVersion=jgit version {0}
lineFormat={0}
listeningOn=Listening on {0}
@@ -129,6 +130,7 @@
metaVar_pass=PASS
metaVar_path=path
metaVar_paths=path ...
+metaVar_pattern=pattern
metaVar_port=PORT
metaVar_ref=REF
metaVar_refs=REFS
@@ -248,6 +250,7 @@
usage_lsRemoteTags=Show only refs starting with refs/tags
usage_LsTree=List the contents of a tree object
usage_MakeCacheTree=Show the current cache tree structure
+usage_Match=Only consider tags matching the given glob(7) pattern or patterns, excluding the "refs/tags/" prefix.
usage_MergeBase=Find as good common ancestors as possible for a merge
usage_MergesTwoDevelopmentHistories=Merges two development histories
usage_PreserveOldPacks=Preserve old pack files by moving them into the preserved subdirectory instead of deleting them after repacking
@@ -255,13 +258,16 @@
usage_ReadDirCache= Read the DirCache 100 times
usage_RebuildCommitGraph=Recreate a repository from another one's commit graph
usage_RebuildRefTree=Copy references into a RefTree
-usage_RebuildRefTreeEnable=set extensions.refsStorage = reftree
+usage_RebuildRefTreeEnable=set extensions.refStorage = reftree
usage_Remote=Manage set of tracked repositories
usage_RepositoryToReadFrom=Repository to read from
usage_RepositoryToReceiveInto=Repository to receive into
usage_RevList=List commit objects in reverse chronological order
usage_RevParse=Pick out and massage parameters
usage_RevParseAll=Show all refs found in refs/
+usage_RevParseVerify=Verify that exactly one parameter is provided, and that it can be turned into \
+a raw 20-byte SHA-1 that can be used to access the object database. If so, emit it to the standard \
+output; otherwise, error out.
usage_S3Bucket=S3 bucket name
usage_S3Expiration=Authorization validity in seconds, default 60 sec
usage_S3Region=S3 region (us-east-1 | us-west-1 | us-west-2 | eu-west-1 |\
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
index c2f3c46..2af1eca 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
@@ -77,7 +77,7 @@ class Checkout extends TextBuiltin {
@Argument(required = false, index = 0, metaVar = "metaVar_name", usage = "usage_checkout")
private String name;
- @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = RestOfArgumentsHandler.class)
+ @Option(name = "--", metaVar = "metaVar_paths", handler = RestOfArgumentsHandler.class)
private List<String> paths = new ArrayList<>();
@Override
@@ -122,17 +122,21 @@ protected void run() throws Exception {
CLIText.get().switchedToBranch,
Repository.shortenRefName(ref.getName())));
} catch (RefNotFoundException e) {
- outw.println(MessageFormat.format(
- CLIText.get().pathspecDidNotMatch,
- name));
+ throw die(MessageFormat
+ .format(CLIText.get().pathspecDidNotMatch, name), e);
} catch (RefAlreadyExistsException e) {
- throw die(MessageFormat.format(CLIText.get().branchAlreadyExists,
- name));
+ throw die(MessageFormat
+ .format(CLIText.get().branchAlreadyExists, name));
} catch (CheckoutConflictException e) {
- outw.println(CLIText.get().checkoutConflict);
- for (String path : e.getConflictingPaths())
- outw.println(MessageFormat.format(
+ StringBuilder builder = new StringBuilder();
+ builder.append(CLIText.get().checkoutConflict);
+ builder.append(System.lineSeparator());
+ for (String path : e.getConflictingPaths()) {
+ builder.append(MessageFormat.format(
CLIText.get().checkoutConflictPathLine, path));
+ builder.append(System.lineSeparator());
+ }
+ throw die(builder.toString(), e);
}
}
}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
index ca5205a..a8eb474 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
@@ -101,6 +101,9 @@ protected void run() throws Exception {
if (localName == null) {
try {
localName = uri.getHumanishName();
+ if (isBare) {
+ localName = localName + Constants.DOT_GIT_EXT;
+ }
localNameF = new File(SystemReader.getInstance().getProperty(
Constants.OS_USER_DIR), localName);
} catch (IllegalArgumentException e) {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandRef.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandRef.java
index 5222515..e5f8532 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandRef.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandRef.java
@@ -165,4 +165,12 @@ public TextBuiltin create() {
r.setCommandName(getName());
return r;
}
+
+ @SuppressWarnings("nls")
+ @Override
+ public String toString() {
+ return "CommandRef [impl=" + impl + ", name=" + name + ", usage="
+ + CLIText.get().resourceBundle().getString(usage) + ", common="
+ + common + "]";
+ }
}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
index faae13a..f5c3f9a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Config.java
@@ -122,4 +122,4 @@ private void list(StoredConfig config) throws IOException,
}
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
index 1008593..51bb979 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java
@@ -86,17 +86,17 @@ class Daemon extends TextBuiltin {
@Option(name = "--timeout", metaVar = "metaVar_seconds", usage = "usage_abortConnectionIfNoActivity")
int timeout = -1;
- @Option(name = "--enable", metaVar = "metaVar_service", usage = "usage_enableTheServiceInAllRepositories", multiValued = true)
- final List<String> enable = new ArrayList<>();
+ @Option(name = "--enable", metaVar = "metaVar_service", usage = "usage_enableTheServiceInAllRepositories")
+ List<String> enable = new ArrayList<>();
- @Option(name = "--disable", metaVar = "metaVar_service", usage = "usage_disableTheServiceInAllRepositories", multiValued = true)
- final List<String> disable = new ArrayList<>();
+ @Option(name = "--disable", metaVar = "metaVar_service", usage = "usage_disableTheServiceInAllRepositories")
+ List<String> disable = new ArrayList<>();
- @Option(name = "--allow-override", metaVar = "metaVar_service", usage = "usage_configureTheServiceInDaemonServicename", multiValued = true)
- final List<String> canOverride = new ArrayList<>();
+ @Option(name = "--allow-override", metaVar = "metaVar_service", usage = "usage_configureTheServiceInDaemonServicename")
+ List<String> canOverride = new ArrayList<>();
- @Option(name = "--forbid-override", metaVar = "metaVar_service", usage = "usage_configureTheServiceInDaemonServicename", multiValued = true)
- final List<String> forbidOverride = new ArrayList<>();
+ @Option(name = "--forbid-override", metaVar = "metaVar_service", usage = "usage_configureTheServiceInDaemonServicename")
+ List<String> forbidOverride = new ArrayList<>();
@Option(name = "--export-all", usage = "usage_exportWithoutGitDaemonExportOk")
boolean exportAll;
@@ -109,7 +109,7 @@ enum KetchServerType {
}
@Argument(required = true, metaVar = "metaVar_directory", usage = "usage_directoriesToExport")
- final List<File> directory = new ArrayList<>();
+ List<File> directory = new ArrayList<>();
@Override
protected boolean requiresRepository() {
@@ -204,4 +204,4 @@ public ReceivePack create(DaemonClient req, Repository repo)
}
});
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java
index ec000f3..03e2711 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java
@@ -42,6 +42,9 @@
*/
package org.eclipse.jgit.pgm;
+import java.util.ArrayList;
+import java.util.List;
+
import org.eclipse.jgit.api.DescribeCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.RefNotFoundException;
@@ -59,6 +62,9 @@ class Describe extends TextBuiltin {
@Option(name = "--long", usage = "usage_LongFormat")
private boolean longDesc;
+ @Option(name = "--match", usage = "usage_Match", metaVar = "metaVar_pattern")
+ private List<String> patterns = new ArrayList<>();
+
@Override
protected void run() throws Exception {
try (Git git = new Git(db)) {
@@ -66,6 +72,7 @@ protected void run() throws Exception {
if (tree != null)
cmd.setTarget(tree);
cmd.setLong(longDesc);
+ cmd.setMatch(patterns.toArray(new String[patterns.size()]));
String result = null;
try {
result = cmd.call();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
index 61a385d..16284d5 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java
@@ -89,7 +89,7 @@ class Diff extends TextBuiltin {
@Option(name = "--cached", usage = "usage_cached")
private boolean cached;
- @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = PathTreeFilterHandler.class)
+ @Option(name = "--", metaVar = "metaVar_paths", handler = PathTreeFilterHandler.class)
private TreeFilter pathFilter = TreeFilter.ALL;
// BEGIN -- Options shared with Log
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java
index 56b6241..4432405 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java
@@ -67,9 +67,9 @@ void tree_0(final AbstractTreeIterator c) {
}
@Argument(index = 1, metaVar = "metaVar_treeish", required = true)
- private final List<AbstractTreeIterator> trees = new ArrayList<>();
+ private List<AbstractTreeIterator> trees = new ArrayList<>();
- @Option(name = "--", metaVar = "metaVar_path", multiValued = true, handler = PathTreeFilterHandler.class)
+ @Option(name = "--", metaVar = "metaVar_path", handler = PathTreeFilterHandler.class)
private TreeFilter pathFilter = TreeFilter.ALL;
@Override
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java
index 5ed23b9..f4b7708 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java
@@ -53,8 +53,8 @@
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode;
-import org.eclipse.jgit.pgm.internal.CLIText;
import org.eclipse.jgit.lib.TextProgressMonitor;
+import org.eclipse.jgit.pgm.internal.CLIText;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.TagOpt;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java
index 02d61e5..398d305 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java
@@ -67,7 +67,7 @@ class LsTree extends TextBuiltin {
private AbstractTreeIterator tree;
@Argument(index = 1)
- @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = StopOptionHandler.class)
+ @Option(name = "--", metaVar = "metaVar_paths", handler = StopOptionHandler.class)
private List<String> paths = new ArrayList<>();
@Override
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
index c94ba0b..9d44dc0 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java
@@ -77,8 +77,8 @@
import org.eclipse.jgit.util.CachedAuthenticator;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
-import org.kohsuke.args4j.ExampleMode;
import org.kohsuke.args4j.Option;
+import org.kohsuke.args4j.OptionHandlerFilter;
/** Command line entry point. */
public class Main {
@@ -231,7 +231,8 @@ private void execute(final String[] argv) throws Exception {
}
if (argv.length == 0 || help) {
- final String ex = clp.printExample(ExampleMode.ALL, CLIText.get().resourceBundle());
+ final String ex = clp.printExample(OptionHandlerFilter.ALL,
+ CLIText.get().resourceBundle());
writer.println("jgit" + ex + " command [ARG ...]"); //$NON-NLS-1$ //$NON-NLS-2$
if (help) {
writer.println();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java
index f8bae1d..975d5de 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java
@@ -63,7 +63,7 @@ void commit_0(final RevCommit c) {
}
@Argument(index = 1, metaVar = "metaVar_commitish", required = true)
- private final List<RevCommit> commits = new ArrayList<>();
+ private List<RevCommit> commits = new ArrayList<>();
@Override
protected void run() throws Exception {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
index 1b805d1..389708e 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java
@@ -77,7 +77,7 @@ class Push extends TextBuiltin {
private String remote = Constants.DEFAULT_REMOTE_NAME;
@Argument(index = 1, metaVar = "metaVar_refspec")
- private final List<RefSpec> refSpecs = new ArrayList<>();
+ private List<RefSpec> refSpecs = new ArrayList<>();
@Option(name = "--all")
private boolean all;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java
index 9cee37b..4c19883 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java
@@ -69,7 +69,7 @@ class Reset extends TextBuiltin {
private String commit;
@Argument(required = false, index = 1, metaVar = "metaVar_paths")
- @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = RestOfArgumentsHandler.class)
+ @Option(name = "--", metaVar = "metaVar_paths", handler = RestOfArgumentsHandler.class)
private List<String> paths = new ArrayList<>();
@Override
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java
index a66b7fa..2157034 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java
@@ -68,7 +68,7 @@ class RevParse extends TextBuiltin {
boolean verify;
@Argument(index = 0, metaVar = "metaVar_commitish")
- private final List<ObjectId> commits = new ArrayList<>();
+ private List<ObjectId> commits = new ArrayList<>();
@Override
protected void run() throws Exception {
@@ -86,7 +86,8 @@ protected void run() throws Exception {
} else {
if (verify && commits.size() > 1) {
final CmdLineParser clp = new CmdLineParser(this);
- throw new CmdLineException(clp, CLIText.get().needSingleRevision);
+ throw new CmdLineException(clp,
+ CLIText.format(CLIText.get().needSingleRevision));
}
for (final ObjectId o : commits) {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java
index 74135e4..6b0744d 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java
@@ -124,9 +124,9 @@ void enableBoundary(final boolean on) {
private String followPath;
@Argument(index = 0, metaVar = "metaVar_commitish")
- private final List<RevCommit> commits = new ArrayList<>();
+ private List<RevCommit> commits = new ArrayList<>();
- @Option(name = "--", metaVar = "metaVar_path", multiValued = true, handler = PathTreeFilterHandler.class)
+ @Option(name = "--", metaVar = "metaVar_path", handler = PathTreeFilterHandler.class)
protected TreeFilter pathFilter = TreeFilter.ALL;
private final List<RevFilter> revLimiter = new ArrayList<>();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java
index 79c3f09..32a5631 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java
@@ -55,12 +55,10 @@
@Command(usage = "usage_StopTrackingAFile", common = true)
class Rm extends TextBuiltin {
- @Argument(metaVar = "metaVar_path", usage = "usage_path", multiValued = true, required = true)
-
+ @Argument(metaVar = "metaVar_path", usage = "usage_path", required = true)
@Option(name = "--", handler = StopOptionHandler.class)
private List<String> paths = new ArrayList<>();
-
@Override
protected void run() throws Exception {
try (Git git = new Git(db)) {
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java
index 6892c99..5eda36f 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java
@@ -87,7 +87,7 @@ class Show extends TextBuiltin {
@Argument(index = 0, metaVar = "metaVar_object")
private String objectName;
- @Option(name = "--", metaVar = "metaVar_path", multiValued = true, handler = PathTreeFilterHandler.class)
+ @Option(name = "--", metaVar = "metaVar_path", handler = PathTreeFilterHandler.class)
protected TreeFilter pathFilter = TreeFilter.ALL;
// BEGIN -- Options shared with Diff
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java
index b7f5e58..adcfea4 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java
@@ -83,7 +83,7 @@ class Status extends TextBuiltin {
protected String untrackedFilesMode = "all"; // default value //$NON-NLS-1$
@Argument(required = false, index = 0, metaVar = "metaVar_paths")
- @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = RestOfArgumentsHandler.class)
+ @Option(name = "--", metaVar = "metaVar_paths", handler = RestOfArgumentsHandler.class)
protected List<String> filterPaths;
@Override
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java
index 0dc549c..c3b45e8 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java
@@ -139,6 +139,31 @@ protected boolean requiresRepository() {
}
/**
+ * Initializes the command to work with a repository, including setting the
+ * output and error streams.
+ *
+ * @param repository
+ * the opened repository that the command should work on.
+ * @param gitDir
+ * value of the {@code --git-dir} command line option, if
+ * {@code repository} is null.
+ * @param input
+ * input stream from which input will be read
+ * @param output
+ * output stream to which output will be written
+ * @param error
+ * error stream to which errors will be written
+ * @since 4.9
+ */
+ public void initRaw(final Repository repository, final String gitDir,
+ InputStream input, OutputStream output, OutputStream error) {
+ this.ins = input;
+ this.outs = output;
+ this.errs = error;
+ init(repository, gitDir);
+ }
+
+ /**
* Initialize the command to work with a repository.
*
* @param repository
@@ -285,6 +310,14 @@ public ThrowingPrintWriter getErrorWriter() {
}
/**
+ * @return output writer, typically this is standard output.
+ * @since 4.9
+ */
+ public ThrowingPrintWriter getOutputWriter() {
+ return outw;
+ }
+
+ /**
* @return the resource bundle that will be passed to args4j for purpose of
* string localization
*/
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java
new file mode 100644
index 0000000..71c8db8
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.pgm.debug;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.MASTER;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import org.eclipse.jgit.internal.storage.io.BlockSource;
+import org.eclipse.jgit.internal.storage.reftable.RefCursor;
+import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.pgm.Command;
+import org.eclipse.jgit.pgm.TextBuiltin;
+import org.eclipse.jgit.util.RefList;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+@Command
+class BenchmarkReftable extends TextBuiltin {
+ enum Test {
+ SCAN,
+ SEEK_COLD, SEEK_HOT,
+ BY_ID_COLD, BY_ID_HOT;
+ }
+
+ @Option(name = "--tries")
+ private int tries = 10;
+
+ @Option(name = "--test")
+ private Test test = Test.SCAN;
+
+ @Option(name = "--ref")
+ private String ref;
+
+ @Option(name = "--object-id")
+ private String objectId;
+
+ @Argument(index = 0)
+ private String lsRemotePath;
+
+ @Argument(index = 1)
+ private String reftablePath;
+
+ @Override
+ protected void run() throws Exception {
+ switch (test) {
+ case SCAN:
+ scan();
+ break;
+
+ case SEEK_COLD:
+ seekCold(ref);
+ break;
+ case SEEK_HOT:
+ seekHot(ref);
+ break;
+
+ case BY_ID_COLD:
+ byIdCold(ObjectId.fromString(objectId));
+ break;
+ case BY_ID_HOT:
+ byIdHot(ObjectId.fromString(objectId));
+ break;
+ }
+ }
+
+ private void printf(String fmt, Object... args) throws IOException {
+ errw.println(String.format(fmt, args));
+ }
+
+ @SuppressWarnings({ "nls", "boxing" })
+ private void scan() throws Exception {
+ long start, tot;
+
+ start = System.currentTimeMillis();
+ for (int i = 0; i < tries; i++) {
+ readLsRemote();
+ }
+ tot = System.currentTimeMillis() - start;
+ printf("%12s %10d ms %6d ms/run", "packed-refs", tot, tot / tries);
+
+ start = System.currentTimeMillis();
+ for (int i = 0; i < tries; i++) {
+ try (FileInputStream in = new FileInputStream(reftablePath);
+ BlockSource src = BlockSource.from(in);
+ ReftableReader reader = new ReftableReader(src)) {
+ try (RefCursor rc = reader.allRefs()) {
+ while (rc.next()) {
+ rc.getRef();
+ }
+ }
+ }
+ }
+ tot = System.currentTimeMillis() - start;
+ printf("%12s %10d ms %6d ms/run", "reftable", tot, tot / tries);
+ }
+
+ private RefList<Ref> readLsRemote()
+ throws IOException, FileNotFoundException {
+ RefList.Builder<Ref> list = new RefList.Builder<>();
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(
+ new FileInputStream(lsRemotePath), UTF_8))) {
+ Ref last = null;
+ String line;
+ while ((line = br.readLine()) != null) {
+ ObjectId id = ObjectId.fromString(line.substring(0, 40));
+ String name = line.substring(41, line.length());
+ if (last != null && name.endsWith("^{}")) { //$NON-NLS-1$
+ last = new ObjectIdRef.PeeledTag(PACKED, last.getName(),
+ last.getObjectId(), id);
+ list.set(list.size() - 1, last);
+ continue;
+ }
+
+ if (name.equals(HEAD)) {
+ last = new SymbolicRef(name, new ObjectIdRef.Unpeeled(NEW,
+ R_HEADS + MASTER, null));
+ } else {
+ last = new ObjectIdRef.PeeledNonTag(PACKED, name, id);
+ }
+ list.add(last);
+ }
+ }
+ list.sort();
+ return list.toRefList();
+ }
+
+ @SuppressWarnings({ "nls", "boxing" })
+ private void seekCold(String refName) throws Exception {
+ long start, tot;
+
+ int lsTries = Math.min(tries, 64);
+ start = System.nanoTime();
+ for (int i = 0; i < lsTries; i++) {
+ readLsRemote().get(refName);
+ }
+ tot = System.nanoTime() - start;
+ printf("%12s %10d usec %9.1f usec/run %5d runs", "packed-refs",
+ tot / 1000,
+ (((double) tot) / lsTries) / 1000,
+ lsTries);
+
+ start = System.nanoTime();
+ for (int i = 0; i < tries; i++) {
+ try (FileInputStream in = new FileInputStream(reftablePath);
+ BlockSource src = BlockSource.from(in);
+ ReftableReader reader = new ReftableReader(src)) {
+ try (RefCursor rc = reader.seekRef(refName)) {
+ while (rc.next()) {
+ rc.getRef();
+ }
+ }
+ }
+ }
+ tot = System.nanoTime() - start;
+ printf("%12s %10d usec %9.1f usec/run %5d runs", "reftable",
+ tot / 1000,
+ (((double) tot) / tries) / 1000,
+ tries);
+ }
+
+ @SuppressWarnings({ "nls", "boxing" })
+ private void seekHot(String refName) throws Exception {
+ long start, tot;
+
+ int lsTries = Math.min(tries, 64);
+ start = System.nanoTime();
+ RefList<Ref> lsRemote = readLsRemote();
+ for (int i = 0; i < lsTries; i++) {
+ lsRemote.get(refName);
+ }
+ tot = System.nanoTime() - start;
+ printf("%12s %10d usec %9.1f usec/run %5d runs", "packed-refs",
+ tot / 1000, (((double) tot) / lsTries) / 1000, lsTries);
+
+ start = System.nanoTime();
+ try (FileInputStream in = new FileInputStream(reftablePath);
+ BlockSource src = BlockSource.from(in);
+ ReftableReader reader = new ReftableReader(src)) {
+ for (int i = 0; i < tries; i++) {
+ try (RefCursor rc = reader.seekRef(refName)) {
+ while (rc.next()) {
+ rc.getRef();
+ }
+ }
+ }
+ }
+ tot = System.nanoTime() - start;
+ printf("%12s %10d usec %9.1f usec/run %5d runs", "reftable",
+ tot / 1000, (((double) tot) / tries) / 1000, tries);
+ }
+
+ @SuppressWarnings({ "nls", "boxing" })
+ private void byIdCold(ObjectId id) throws Exception {
+ long start, tot;
+
+ int lsTries = Math.min(tries, 64);
+ start = System.nanoTime();
+ for (int i = 0; i < lsTries; i++) {
+ for (Ref r : readLsRemote()) {
+ if (id.equals(r.getObjectId())) {
+ continue;
+ }
+ }
+ }
+ tot = System.nanoTime() - start;
+ printf("%12s %10d usec %9.1f usec/run %5d runs", "packed-refs",
+ tot / 1000, (((double) tot) / lsTries) / 1000, lsTries);
+
+ start = System.nanoTime();
+ for (int i = 0; i < tries; i++) {
+ try (FileInputStream in = new FileInputStream(reftablePath);
+ BlockSource src = BlockSource.from(in);
+ ReftableReader reader = new ReftableReader(src)) {
+ try (RefCursor rc = reader.byObjectId(id)) {
+ while (rc.next()) {
+ rc.getRef();
+ }
+ }
+ }
+ }
+ tot = System.nanoTime() - start;
+ printf("%12s %10d usec %9.1f usec/run %5d runs", "reftable",
+ tot / 1000, (((double) tot) / tries) / 1000, tries);
+ }
+
+ @SuppressWarnings({ "nls", "boxing" })
+ private void byIdHot(ObjectId id) throws Exception {
+ long start, tot;
+
+ int lsTries = Math.min(tries, 64);
+ start = System.nanoTime();
+ RefList<Ref> lsRemote = readLsRemote();
+ for (int i = 0; i < lsTries; i++) {
+ for (Ref r : lsRemote) {
+ if (id.equals(r.getObjectId())) {
+ continue;
+ }
+ }
+ }
+ tot = System.nanoTime() - start;
+ printf("%12s %10d usec %9.1f usec/run %5d runs", "packed-refs",
+ tot / 1000, (((double) tot) / lsTries) / 1000, lsTries);
+
+ start = System.nanoTime();
+ try (FileInputStream in = new FileInputStream(reftablePath);
+ BlockSource src = BlockSource.from(in);
+ ReftableReader reader = new ReftableReader(src)) {
+ for (int i = 0; i < tries; i++) {
+ try (RefCursor rc = reader.byObjectId(id)) {
+ while (rc.next()) {
+ rc.getRef();
+ }
+ }
+ }
+ }
+ tot = System.nanoTime() - start;
+ printf("%12s %10d usec %9.1f usec/run %5d runs", "reftable",
+ tot / 1000, (((double) tot) / tries) / 1000, tries);
+ }
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
index 44ec3f4..8de57e3 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java
@@ -114,13 +114,13 @@ DiffAlgorithm create() {
//
//
- @Option(name = "--algorithm", multiValued = true, metaVar = "NAME", usage = "Enable algorithm(s)")
+ @Option(name = "--algorithm", metaVar = "NAME", usage = "Enable algorithm(s)")
List<String> algorithms = new ArrayList<>();
@Option(name = "--text-limit", metaVar = "LIMIT", usage = "Maximum size in KiB to scan per file revision")
int textLimit = 15 * 1024; // 15 MiB as later we do * 1024.
- @Option(name = "--repository", aliases = { "-r" }, multiValued = true, metaVar = "GIT_DIR", usage = "Repository to scan")
+ @Option(name = "--repository", aliases = { "-r" }, metaVar = "GIT_DIR", usage = "Repository to scan")
List<File> gitDirs = new ArrayList<>();
@Option(name = "--count", metaVar = "LIMIT", usage = "Number of file revisions to be compared")
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/LfsStore.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/LfsStore.java
index 5839f33..9dc4721 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/LfsStore.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/LfsStore.java
@@ -82,7 +82,7 @@ class LfsStore extends TextBuiltin {
/**
* Tiny web application server for testing
*/
- class AppServer {
+ static class AppServer {
private final Server server;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadReftable.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadReftable.java
new file mode 100644
index 0000000..9b8db3e
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadReftable.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.pgm.debug;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.eclipse.jgit.internal.storage.io.BlockSource;
+import org.eclipse.jgit.internal.storage.reftable.RefCursor;
+import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.pgm.Command;
+import org.eclipse.jgit.pgm.TextBuiltin;
+import org.kohsuke.args4j.Argument;
+
+@Command
+class ReadReftable extends TextBuiltin {
+ @Argument(index = 0)
+ private String input;
+
+ @Argument(index = 1, required = false)
+ private String ref;
+
+ @Override
+ protected void run() throws Exception {
+ try (FileInputStream in = new FileInputStream(input);
+ BlockSource src = BlockSource.from(in);
+ ReftableReader reader = new ReftableReader(src)) {
+ try (RefCursor rc = ref != null
+ ? reader.seekRef(ref)
+ : reader.allRefs()) {
+ while (rc.next()) {
+ write(rc.getRef());
+ }
+ }
+ }
+ }
+
+ private void write(Ref r) throws IOException {
+ if (r.isSymbolic()) {
+ outw.println(r.getTarget().getName() + '\t' + r.getName());
+ return;
+ }
+
+ ObjectId id1 = r.getObjectId();
+ if (id1 != null) {
+ outw.println(id1.name() + '\t' + r.getName());
+ }
+
+ ObjectId id2 = r.getPeeledObjectId();
+ if (id2 != null) {
+ outw.println('^' + id2.name());
+ }
+ }
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java
index 57345e2..8cde513 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java
@@ -133,7 +133,7 @@ protected void run() throws Exception {
if (enable && !(db.getRefDatabase() instanceof RefTreeDatabase)) {
StoredConfig cfg = db.getConfig();
cfg.setInt("core", null, "repositoryformatversion", 1); //$NON-NLS-1$ //$NON-NLS-2$
- cfg.setString("extensions", null, "refsStorage", "reftree"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ cfg.setString("extensions", null, "refStorage", "reftree"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
cfg.save();
errw.println("Enabled reftree."); //$NON-NLS-1$
errw.flush();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
index 0eb4e05..ce58201 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java
@@ -250,16 +250,16 @@ public int fold(int hash, int bits) {
//
//
- @Option(name = "--hash", multiValued = true, metaVar = "NAME", usage = "Enable hash function(s)")
+ @Option(name = "--hash", metaVar = "NAME", usage = "Enable hash function(s)")
List<String> hashFunctions = new ArrayList<>();
- @Option(name = "--fold", multiValued = true, metaVar = "NAME", usage = "Enable fold function(s)")
+ @Option(name = "--fold", metaVar = "NAME", usage = "Enable fold function(s)")
List<String> foldFunctions = new ArrayList<>();
@Option(name = "--text-limit", metaVar = "LIMIT", usage = "Maximum size in KiB to scan")
int textLimit = 15 * 1024; // 15 MiB as later we do * 1024.
- @Option(name = "--repository", aliases = { "-r" }, multiValued = true, metaVar = "GIT_DIR", usage = "Repository to scan")
+ @Option(name = "--repository", aliases = { "-r" }, metaVar = "GIT_DIR", usage = "Repository to scan")
List<File> gitDirs = new ArrayList<>();
@Override
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/VerifyReftable.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/VerifyReftable.java
new file mode 100644
index 0000000..dffb579
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/VerifyReftable.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.pgm.debug;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import org.eclipse.jgit.internal.storage.io.BlockSource;
+import org.eclipse.jgit.internal.storage.reftable.RefCursor;
+import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefComparator;
+import org.eclipse.jgit.lib.TextProgressMonitor;
+import org.eclipse.jgit.pgm.Command;
+import org.eclipse.jgit.pgm.TextBuiltin;
+import org.kohsuke.args4j.Argument;
+
+@Command
+class VerifyReftable extends TextBuiltin {
+ private static final long SEED1 = 0xaba8bb4de4caf86cL;
+ private static final long SEED2 = 0x28bb5c25ad43ecb5L;
+
+ @Argument(index = 0)
+ private String lsRemotePath;
+
+ @Argument(index = 1)
+ private String reftablePath;
+
+ @Override
+ protected void run() throws Exception {
+ List<Ref> refs = WriteReftable.readRefs(lsRemotePath);
+
+ try (FileInputStream in = new FileInputStream(reftablePath);
+ BlockSource src = BlockSource.from(in);
+ ReftableReader reader = new ReftableReader(src)) {
+ scan(refs, reader);
+ seek(refs, reader);
+ byId(refs, reader);
+ }
+ }
+
+ @SuppressWarnings("nls")
+ private void scan(List<Ref> refs, ReftableReader reader)
+ throws IOException {
+ errw.print(String.format("%-20s", "sequential scan..."));
+ errw.flush();
+ try (RefCursor rc = reader.allRefs()) {
+ for (Ref exp : refs) {
+ verify(exp, rc);
+ }
+ if (rc.next()) {
+ throw die("expected end of table");
+ }
+ }
+ errw.println(" OK");
+ }
+
+ @SuppressWarnings("nls")
+ private void seek(List<Ref> refs, ReftableReader reader)
+ throws IOException {
+ List<Ref> rnd = new ArrayList<>(refs);
+ Collections.shuffle(rnd, new Random(SEED1));
+
+ TextProgressMonitor pm = new TextProgressMonitor(errw);
+ pm.beginTask("random seek", rnd.size());
+ for (Ref exp : rnd) {
+ try (RefCursor rc = reader.seekRef(exp.getName())) {
+ verify(exp, rc);
+ if (rc.next()) {
+ throw die("should not have ref after " + exp.getName());
+ }
+ }
+ pm.update(1);
+ }
+ pm.endTask();
+ }
+
+ @SuppressWarnings("nls")
+ private void byId(List<Ref> refs, ReftableReader reader)
+ throws IOException {
+ Map<ObjectId, List<Ref>> want = groupById(refs);
+ List<List<Ref>> rnd = new ArrayList<>(want.values());
+ Collections.shuffle(rnd, new Random(SEED2));
+
+ TextProgressMonitor pm = new TextProgressMonitor(errw);
+ pm.beginTask("byObjectId", rnd.size());
+ for (List<Ref> exp : rnd) {
+ Collections.sort(exp, RefComparator.INSTANCE);
+ ObjectId id = exp.get(0).getObjectId();
+ try (RefCursor rc = reader.byObjectId(id)) {
+ for (Ref r : exp) {
+ verify(r, rc);
+ }
+ }
+ pm.update(1);
+ }
+ pm.endTask();
+ }
+
+ private static Map<ObjectId, List<Ref>> groupById(List<Ref> refs) {
+ Map<ObjectId, List<Ref>> m = new HashMap<>();
+ for (Ref r : refs) {
+ ObjectId id = r.getObjectId();
+ if (id != null) {
+ List<Ref> c = m.get(id);
+ if (c == null) {
+ c = new ArrayList<>(2);
+ m.put(id, c);
+ }
+ c.add(r);
+ }
+ }
+ return m;
+ }
+
+ @SuppressWarnings("nls")
+ private void verify(Ref exp, RefCursor rc) throws IOException {
+ if (!rc.next()) {
+ throw die("ended before " + exp.getName());
+ }
+
+ Ref act = rc.getRef();
+ if (!exp.getName().equals(act.getName())) {
+ throw die(String.format("expected %s, found %s",
+ exp.getName(),
+ act.getName()));
+ }
+
+ if (exp.isSymbolic()) {
+ if (!act.isSymbolic()) {
+ throw die("expected " + act.getName() + " to be symbolic");
+ }
+ if (!exp.getTarget().getName().equals(act.getTarget().getName())) {
+ throw die(String.format("expected %s to be %s, found %s",
+ exp.getName(),
+ exp.getLeaf().getName(),
+ act.getLeaf().getName()));
+ }
+ return;
+ }
+
+ if (!AnyObjectId.equals(exp.getObjectId(), act.getObjectId())) {
+ throw die(String.format("expected %s to be %s, found %s",
+ exp.getName(),
+ id(exp.getObjectId()),
+ id(act.getObjectId())));
+ }
+
+ if (exp.getPeeledObjectId() != null
+ && !AnyObjectId.equals(exp.getPeeledObjectId(), act.getPeeledObjectId())) {
+ throw die(String.format("expected %s to be %s, found %s",
+ exp.getName(),
+ id(exp.getPeeledObjectId()),
+ id(act.getPeeledObjectId())));
+ }
+ }
+
+ @SuppressWarnings("nls")
+ private static String id(ObjectId id) {
+ return id != null ? id.name() : "<null>";
+ }
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteReftable.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteReftable.java
new file mode 100644
index 0000000..76ffa19
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteReftable.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.pgm.debug;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.MASTER;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
+import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.pgm.Command;
+import org.eclipse.jgit.pgm.TextBuiltin;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+@Command
+class WriteReftable extends TextBuiltin {
+ private static final int KIB = 1 << 10;
+ private static final int MIB = 1 << 20;
+
+ @Option(name = "--block-size")
+ private int refBlockSize;
+
+ @Option(name = "--log-block-size")
+ private int logBlockSize;
+
+ @Option(name = "--restart-interval")
+ private int restartInterval;
+
+ @Option(name = "--index-levels")
+ private int indexLevels;
+
+ @Option(name = "--reflog-in")
+ private String reflogIn;
+
+ @Option(name = "--no-index-objects")
+ private boolean noIndexObjects;
+
+ @Argument(index = 0)
+ private String in;
+
+ @Argument(index = 1)
+ private String out;
+
+ @SuppressWarnings({ "nls", "boxing" })
+ @Override
+ protected void run() throws Exception {
+ List<Ref> refs = readRefs(in);
+ List<LogEntry> logs = readLog(reflogIn);
+
+ ReftableWriter.Stats stats;
+ try (OutputStream os = new FileOutputStream(out)) {
+ ReftableConfig cfg = new ReftableConfig();
+ cfg.setIndexObjects(!noIndexObjects);
+ if (refBlockSize > 0) {
+ cfg.setRefBlockSize(refBlockSize);
+ }
+ if (logBlockSize > 0) {
+ cfg.setLogBlockSize(logBlockSize);
+ }
+ if (restartInterval > 0) {
+ cfg.setRestartInterval(restartInterval);
+ }
+ if (indexLevels > 0) {
+ cfg.setMaxIndexLevels(indexLevels);
+ }
+
+ ReftableWriter w = new ReftableWriter(cfg);
+ w.setMinUpdateIndex(min(logs)).setMaxUpdateIndex(max(logs));
+ w.begin(os);
+ w.sortAndWriteRefs(refs);
+ for (LogEntry e : logs) {
+ w.writeLog(e.ref, e.updateIndex, e.who,
+ e.oldId, e.newId, e.message);
+ }
+ stats = w.finish().getStats();
+ }
+
+ double fileMiB = ((double) stats.totalBytes()) / MIB;
+ printf("Summary:");
+ printf(" file sz : %.1f MiB (%d bytes)", fileMiB, stats.totalBytes());
+ printf(" padding : %d KiB", stats.paddingBytes() / KIB);
+ errw.println();
+
+ printf("Refs:");
+ printf(" ref blk : %d", stats.refBlockSize());
+ printf(" restarts: %d", stats.restartInterval());
+ printf(" refs : %d", stats.refCount());
+ if (stats.refIndexLevels() > 0) {
+ int idxSize = (int) Math.round(((double) stats.refIndexSize()) / KIB);
+ printf(" idx sz : %d KiB", idxSize);
+ printf(" idx lvl : %d", stats.refIndexLevels());
+ }
+ printf(" avg ref : %d bytes", stats.refBytes() / refs.size());
+ errw.println();
+
+ if (stats.objCount() > 0) {
+ int objMiB = (int) Math.round(((double) stats.objBytes()) / MIB);
+ int idLen = stats.objIdLength();
+ printf("Objects:");
+ printf(" obj blk : %d", stats.refBlockSize());
+ printf(" restarts: %d", stats.restartInterval());
+ printf(" objects : %d", stats.objCount());
+ printf(" obj sz : %d MiB (%d bytes)", objMiB, stats.objBytes());
+ if (stats.objIndexSize() > 0) {
+ int s = (int) Math.round(((double) stats.objIndexSize()) / KIB);
+ printf(" idx sz : %d KiB", s);
+ printf(" idx lvl : %d", stats.objIndexLevels());
+ }
+ printf(" id len : %d bytes (%d hex digits)", idLen, 2 * idLen);
+ printf(" avg obj : %d bytes", stats.objBytes() / stats.objCount());
+ errw.println();
+ }
+ if (stats.logCount() > 0) {
+ int logMiB = (int) Math.round(((double) stats.logBytes()) / MIB);
+ printf("Log:");
+ printf(" log blk : %d", stats.logBlockSize());
+ printf(" logs : %d", stats.logCount());
+ printf(" log sz : %d MiB (%d bytes)", logMiB, stats.logBytes());
+ printf(" avg log : %d bytes", stats.logBytes() / logs.size());
+ errw.println();
+ }
+ }
+
+ private void printf(String fmt, Object... args) throws IOException {
+ errw.println(String.format(fmt, args));
+ }
+
+ static List<Ref> readRefs(String inputFile) throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ try (BufferedReader br = new BufferedReader(
+ new InputStreamReader(new FileInputStream(inputFile), UTF_8))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ ObjectId id = ObjectId.fromString(line.substring(0, 40));
+ String name = line.substring(41, line.length());
+ if (name.endsWith("^{}")) { //$NON-NLS-1$
+ int lastIdx = refs.size() - 1;
+ Ref last = refs.get(lastIdx);
+ refs.set(lastIdx, new ObjectIdRef.PeeledTag(PACKED,
+ last.getName(), last.getObjectId(), id));
+ continue;
+ }
+
+ Ref ref;
+ if (name.equals(HEAD)) {
+ ref = new SymbolicRef(name, new ObjectIdRef.Unpeeled(NEW,
+ R_HEADS + MASTER, null));
+ } else {
+ ref = new ObjectIdRef.PeeledNonTag(PACKED, name, id);
+ }
+ refs.add(ref);
+ }
+ }
+ Collections.sort(refs, (a, b) -> a.getName().compareTo(b.getName()));
+ return refs;
+ }
+
+ private static List<LogEntry> readLog(String logPath)
+ throws FileNotFoundException, IOException {
+ if (logPath == null) {
+ return Collections.emptyList();
+ }
+
+ List<LogEntry> log = new ArrayList<>();
+ try (BufferedReader br = new BufferedReader(
+ new InputStreamReader(new FileInputStream(logPath), UTF_8))) {
+ @SuppressWarnings("nls")
+ Pattern pattern = Pattern.compile("([^,]+)" // 1: ref
+ + ",([0-9]+(?:[.][0-9]+)?)" // 2: time
+ + ",([^,]+)" // 3: who
+ + ",([^,]+)" // 4: old
+ + ",([^,]+)" // 5: new
+ + ",(.*)"); // 6: msg
+ String line;
+ while ((line = br.readLine()) != null) {
+ Matcher m = pattern.matcher(line);
+ if (!m.matches()) {
+ throw new IOException("unparsed line: " + line); //$NON-NLS-1$
+ }
+ String ref = m.group(1);
+ double t = Double.parseDouble(m.group(2));
+ long time = ((long) t) * 1000L;
+ long index = (long) (t * 1e6);
+ String user = m.group(3);
+ ObjectId oldId = parseId(m.group(4));
+ ObjectId newId = parseId(m.group(5));
+ String msg = m.group(6);
+ String email = user + "@gerrit"; //$NON-NLS-1$
+ PersonIdent who = new PersonIdent(user, email, time, -480);
+ log.add(new LogEntry(ref, index, who, oldId, newId, msg));
+ }
+ }
+ Collections.sort(log, LogEntry::compare);
+ return log;
+ }
+
+ private static long min(List<LogEntry> log) {
+ return log.stream().mapToLong(e -> e.updateIndex).min().orElse(0);
+ }
+
+ private static long max(List<LogEntry> log) {
+ return log.stream().mapToLong(e -> e.updateIndex).max().orElse(0);
+ }
+
+ private static ObjectId parseId(String s) {
+ if ("NULL".equals(s)) { //$NON-NLS-1$
+ return ObjectId.zeroId();
+ }
+ return ObjectId.fromString(s);
+ }
+
+ private static class LogEntry {
+ static int compare(LogEntry a, LogEntry b) {
+ int cmp = a.ref.compareTo(b.ref);
+ if (cmp == 0) {
+ cmp = Long.signum(b.updateIndex - a.updateIndex);
+ }
+ return cmp;
+ }
+
+ final String ref;
+ final long updateIndex;
+ final PersonIdent who;
+ final ObjectId oldId;
+ final ObjectId newId;
+ final String message;
+
+ LogEntry(String ref, long updateIndex, PersonIdent who,
+ ObjectId oldId, ObjectId newId, String message) {
+ this.ref = ref;
+ this.updateIndex = updateIndex;
+ this.who = who;
+ this.oldId = oldId;
+ this.newId = newId;
+ this.message = message;
+ }
+ }
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
index e012372..dfb489d 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
@@ -45,14 +45,47 @@
package org.eclipse.jgit.pgm.internal;
import java.text.MessageFormat;
+import java.util.Locale;
import org.eclipse.jgit.nls.NLS;
import org.eclipse.jgit.nls.TranslationBundle;
+import org.kohsuke.args4j.Localizable;
/**
* Translation bundle for JGit command line interface
*/
public class CLIText extends TranslationBundle {
+ /**
+ * Formats text strings using {@code Localizable}.
+ *
+ */
+ public static class Format implements Localizable {
+ final String text;
+
+ Format(String text) {
+ this.text = text;
+ }
+
+ @Override
+ public String formatWithLocale(Locale locale, Object... args) {
+ // we don't care about Locale for now
+ return format(args);
+ }
+
+ @Override
+ public String format(Object... args) {
+ return MessageFormat.format(text, args);
+ }
+ }
+
+ /**
+ * @param text
+ * the text to format.
+ * @return a new Format instance.
+ */
+ public static Format format(String text) {
+ return new Format(text);
+ }
/**
* @return an instance of this translation bundle
@@ -151,6 +184,7 @@ public static String fatalError(String message) {
/***/ public String initializedEmptyGitRepositoryIn;
/***/ public String invalidHttpProxyOnlyHttpSupported;
/***/ public String invalidRecurseSubmodulesMode;
+ /***/ public String invalidUntrackedFilesMode;
/***/ public String jgitVersion;
/***/ public String lfsNoAccessKey;
/***/ public String lfsNoSecretKey;
@@ -196,6 +230,7 @@ public static String fatalError(String message) {
/***/ public String metaVar_pass;
/***/ public String metaVar_path;
/***/ public String metaVar_paths;
+ /***/ public String metaVar_pattern;
/***/ public String metaVar_port;
/***/ public String metaVar_ref;
/***/ public String metaVar_refs;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java
index 8f56bda..587f98c 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java
@@ -119,20 +119,25 @@ public int parseArguments(final Parameters params) throws CmdLineException {
try {
id = clp.getRepository().resolve(name);
} catch (IOException e) {
- throw new CmdLineException(clp, e.getMessage());
+ throw new CmdLineException(clp, CLIText.format(e.getMessage()));
}
if (id == null)
- throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
+ throw new CmdLineException(clp,
+ CLIText.format(CLIText.get().notATree), name);
final CanonicalTreeParser p = new CanonicalTreeParser();
try (ObjectReader curs = clp.getRepository().newObjectReader()) {
p.reset(curs, clp.getRevWalk().parseTree(id));
} catch (MissingObjectException e) {
- throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
+ throw new CmdLineException(clp,
+ CLIText.format(CLIText.get().notATree), name);
} catch (IncorrectObjectTypeException e) {
- throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
+ throw new CmdLineException(clp,
+ CLIText.format(CLIText.get().notATree), name);
} catch (IOException e) {
- throw new CmdLineException(clp, MessageFormat.format(CLIText.get().cannotReadBecause, name, e.getMessage()));
+ throw new CmdLineException(clp,
+ CLIText.format(CLIText.get().cannotReadBecause), name,
+ e.getMessage());
}
setter.addValue(p);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java
index 020b625..3dcb2a3 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java
@@ -47,7 +47,6 @@
import java.io.Writer;
import java.lang.reflect.Field;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;
@@ -68,6 +67,7 @@
import org.kohsuke.args4j.NamedOptionDef;
import org.kohsuke.args4j.Option;
import org.kohsuke.args4j.OptionDef;
+import org.kohsuke.args4j.OptionHandlerRegistry;
import org.kohsuke.args4j.spi.OptionHandler;
import org.kohsuke.args4j.spi.RestOfArgumentsHandler;
import org.kohsuke.args4j.spi.Setter;
@@ -82,13 +82,14 @@
*/
public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser {
static {
- registerHandler(AbstractTreeIterator.class,
+ OptionHandlerRegistry registry = OptionHandlerRegistry.getRegistry();
+ registry.registerHandler(AbstractTreeIterator.class,
AbstractTreeIteratorHandler.class);
- registerHandler(ObjectId.class, ObjectIdHandler.class);
- registerHandler(RefSpec.class, RefSpecHandler.class);
- registerHandler(RevCommit.class, RevCommitHandler.class);
- registerHandler(RevTree.class, RevTreeHandler.class);
- registerHandler(List.class, OptionWithValuesListHandler.class);
+ registry.registerHandler(ObjectId.class, ObjectIdHandler.class);
+ registry.registerHandler(RefSpec.class, RefSpecHandler.class);
+ registry.registerHandler(RevCommit.class, RevCommitHandler.class);
+ registry.registerHandler(RevTree.class, RevTreeHandler.class);
+ registry.registerHandler(List.class, OptionWithValuesListHandler.class);
}
private final Repository db;
@@ -267,8 +268,8 @@ public RevWalk getRevWalkGently() {
class MyOptionDef extends OptionDef {
public MyOptionDef(OptionDef o) {
- super(o.usage(), o.metaVar(), o.required(), o.handler(), o
- .isMultiValued());
+ super(o.usage(), o.metaVar(), o.required(), o.help(), o.hidden(),
+ o.handler(), o.isMultiValued());
}
@Override
@@ -300,24 +301,6 @@ protected OptionHandler createOptionHandler(OptionDef o, Setter setter) {
}
- @SuppressWarnings("unchecked")
- private List<OptionHandler> getOptions() {
- List<OptionHandler> options = null;
- try {
- Field field = org.kohsuke.args4j.CmdLineParser.class
- .getDeclaredField("options"); //$NON-NLS-1$
- field.setAccessible(true);
- options = (List<OptionHandler>) field.get(this);
- } catch (NoSuchFieldException | SecurityException
- | IllegalArgumentException | IllegalAccessException e) {
- // ignore
- }
- if (options == null) {
- return Collections.emptyList();
- }
- return options;
- }
-
@Override
public void printSingleLineUsage(Writer w, ResourceBundle rb) {
List<OptionHandler> options = getOptions();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java
index 75ca554..74bab2d 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java
@@ -45,7 +45,6 @@
package org.eclipse.jgit.pgm.opt;
import java.io.IOException;
-import java.text.MessageFormat;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.pgm.internal.CLIText;
@@ -86,14 +85,15 @@ public int parseArguments(final Parameters params) throws CmdLineException {
try {
id = clp.getRepository().resolve(name);
} catch (IOException e) {
- throw new CmdLineException(clp, e.getMessage());
+ throw new CmdLineException(clp, CLIText.format(e.getMessage()));
}
if (id != null) {
setter.addValue(id);
return 1;
}
- throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notAnObject, name));
+ throw new CmdLineException(clp,
+ CLIText.format(CLIText.get().notAnObject), name);
}
@Override
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java
index 7661774..27555e3 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java
@@ -45,7 +45,6 @@
package org.eclipse.jgit.pgm.opt;
import java.io.IOException;
-import java.text.MessageFormat;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -97,9 +96,8 @@ public int parseArguments(final Parameters params) throws CmdLineException {
if (dot2 != -1) {
if (!option.isMultiValued())
throw new CmdLineException(clp,
- MessageFormat.format(
- CLIText.get().onlyOneMetaVarExpectedIn,
- option.metaVar(), name));
+ CLIText.format(CLIText.get().onlyOneMetaVarExpectedIn),
+ option.metaVar(), name);
final String left = name.substring(0, dot2);
final String right = name.substring(dot2 + 2);
@@ -118,20 +116,25 @@ private void addOne(final String name, final boolean interesting)
try {
id = clp.getRepository().resolve(name);
} catch (IOException e) {
- throw new CmdLineException(clp, e.getMessage());
+ throw new CmdLineException(clp, CLIText.format(e.getMessage()));
}
if (id == null)
- throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notACommit, name));
+ throw new CmdLineException(clp,
+ CLIText.format(CLIText.get().notACommit), name);
final RevCommit c;
try {
c = clp.getRevWalk().parseCommit(id);
} catch (MissingObjectException e) {
- throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notACommit, name));
+ throw new CmdLineException(clp,
+ CLIText.format(CLIText.get().notACommit), name);
} catch (IncorrectObjectTypeException e) {
- throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notACommit, name));
+ throw new CmdLineException(clp,
+ CLIText.format(CLIText.get().notACommit), name);
} catch (IOException e) {
- throw new CmdLineException(clp, MessageFormat.format(CLIText.get().cannotReadBecause, name, e.getMessage()));
+ throw new CmdLineException(clp,
+ CLIText.format(CLIText.get().cannotReadBecause), name,
+ e.getMessage());
}
if (interesting)
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java
index 9f1d21e..15ed589 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java
@@ -45,7 +45,6 @@
package org.eclipse.jgit.pgm.opt;
import java.io.IOException;
-import java.text.MessageFormat;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -89,20 +88,25 @@ public int parseArguments(final Parameters params) throws CmdLineException {
try {
id = clp.getRepository().resolve(name);
} catch (IOException e) {
- throw new CmdLineException(clp, e.getMessage());
+ throw new CmdLineException(clp, CLIText.format(e.getMessage()));
}
if (id == null)
- throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
+ throw new CmdLineException(clp,
+ CLIText.format(CLIText.get().notATree), name);
final RevTree c;
try {
c = clp.getRevWalk().parseTree(id);
} catch (MissingObjectException e) {
- throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
+ throw new CmdLineException(clp,
+ CLIText.format(CLIText.get().notATree), name);
} catch (IncorrectObjectTypeException e) {
- throw new CmdLineException(clp, MessageFormat.format(CLIText.get().notATree, name));
+ throw new CmdLineException(clp,
+ CLIText.format(CLIText.get().notATree), name);
} catch (IOException e) {
- throw new CmdLineException(clp, MessageFormat.format(CLIText.get().cannotReadBecause, name, e.getMessage()));
+ throw new CmdLineException(clp,
+ CLIText.format(CLIText.get().cannotReadBecause), name,
+ e.getMessage());
}
setter.addValue(c);
return 1;
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java
index 311597e..ae5263f 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java
@@ -43,8 +43,6 @@
package org.eclipse.jgit.pgm.opt;
-import java.text.MessageFormat;
-
import org.eclipse.jgit.pgm.CommandCatalog;
import org.eclipse.jgit.pgm.CommandRef;
import org.eclipse.jgit.pgm.TextBuiltin;
@@ -85,8 +83,8 @@ public int parseArguments(final Parameters params) throws CmdLineException {
final String name = params.getParameter(0);
final CommandRef cr = CommandCatalog.get(name);
if (cr == null)
- throw new CmdLineException(clp, MessageFormat.format(
- CLIText.get().notAJgitCommand, name));
+ throw new CmdLineException(clp,
+ CLIText.format(CLIText.get().notAJgitCommand), name);
// Force option parsing to stop. Everything after us should
// be arguments known only to this command and must not be
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/UntrackedFilesHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/UntrackedFilesHandler.java
index c4e8b05..e22b2e4 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/UntrackedFilesHandler.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/UntrackedFilesHandler.java
@@ -42,6 +42,7 @@
*/
package org.eclipse.jgit.pgm.opt;
+import org.eclipse.jgit.pgm.internal.CLIText;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.OptionDef;
@@ -102,8 +103,9 @@ public int parseArguments(Parameters params) throws CmdLineException {
if ("no".equals(mode) || "all".equals(mode)) { //$NON-NLS-1$ //$NON-NLS-2$
setter.addValue(mode);
} else {
- throw new CmdLineException(owner, String.format(
- "Invalid untracked files mode '%s'", mode)); //$NON-NLS-1$
+ throw new CmdLineException(owner,
+ CLIText.format(CLIText.get().invalidUntrackedFilesMode),
+ mode);
}
return 1;
} else {
@@ -111,4 +113,4 @@ public int parseArguments(Parameters params) throws CmdLineException {
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.test/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.test/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.test/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.test/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.test/.settings/org.eclipse.pde.api.tools.prefs
index cd148d9..c0030de 100644
--- a/org.eclipse.jgit.test/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.test/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,4 +1,4 @@
-#Tue Oct 18 00:52:01 CEST 2011
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -8,6 +8,10 @@
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -47,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -58,6 +63,7 @@
INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
INVALID_JAVADOC_TAG=Ignore
INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
LEAK_EXTEND=Warning
@@ -75,6 +81,7 @@
METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
@@ -83,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index 7bacb05..4e064f8 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -2,54 +2,58 @@
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit.test
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-Localization: plugin
Bundle-Vendor: %provider_name
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
- org.eclipse.jgit.api;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.api.errors;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.attributes;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.awtui;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.blame;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.diff;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.dircache;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.errors;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.events;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.fnmatch;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.gitrepo;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.hooks;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.ignore;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.ignore.internal;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.file;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.pack;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.internal.storage.reftree;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.junit;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lfs;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.merge;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.notes;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.patch;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.pgm;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.pgm.internal;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.revplot;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.revwalk.filter;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.storage.file;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.storage.pack;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.submodule;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport.http;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport.resolver;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.treewalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.treewalk.filter;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util.io;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util.sha1;version="[4.8.1,4.9.0)",
+ com.jcraft.jsch;version="[0.1.54,0.2.0)",
+ org.eclipse.jgit.api;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.api.errors;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.attributes;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.awtui;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.blame;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.diff;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.dircache;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.errors;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.events;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.fnmatch;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.gitrepo;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.hooks;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.ignore;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.ignore.internal;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.fsck;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.file;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.io;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.internal.storage.reftree;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.junit;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lfs;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.merge;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.nls;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.notes;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.patch;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.pgm;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.pgm.internal;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.revplot;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.revwalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.revwalk.filter;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.storage.file;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.storage.pack;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.submodule;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport.http;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport.resolver;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.treewalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util.io;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util.sha1;version="[4.9.9,4.10.0)",
org.junit;version="[4.4.0,5.0.0)",
org.junit.experimental.theories;version="[4.4.0,5.0.0)",
org.junit.rules;version="[4.11.0,5.0.0)",
diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml
index c373112..5b39d5e 100644
--- a/org.eclipse.jgit.test/pom.xml
+++ b/org.eclipse.jgit.test/pom.xml
@@ -52,7 +52,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.test</artifactId>
@@ -66,7 +66,6 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
- <scope>test</scope>
</dependency>
<!-- Optional security provider for encryption tests. -->
diff --git a/org.eclipse.jgit.test/src/org/eclipse/jgit/events/ChangeRecorder.java b/org.eclipse.jgit.test/src/org/eclipse/jgit/events/ChangeRecorder.java
new file mode 100644
index 0000000..c5582a8
--- /dev/null
+++ b/org.eclipse.jgit.test/src/org/eclipse/jgit/events/ChangeRecorder.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.events;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A {@link WorkingTreeModifiedListener} that can be used in tests to check
+ * expected events.
+ */
+public class ChangeRecorder implements WorkingTreeModifiedListener {
+
+ public static final String[] EMPTY = new String[0];
+
+ private Set<String> modified = new HashSet<>();
+
+ private Set<String> deleted = new HashSet<>();
+
+ private int eventCount;
+
+ @Override
+ public void onWorkingTreeModified(WorkingTreeModifiedEvent event) {
+ eventCount++;
+ modified.removeAll(event.getDeleted());
+ deleted.removeAll(event.getModified());
+ modified.addAll(event.getModified());
+ deleted.addAll(event.getDeleted());
+ }
+
+ private String[] getModified() {
+ return modified.toArray(new String[modified.size()]);
+ }
+
+ private String[] getDeleted() {
+ return deleted.toArray(new String[deleted.size()]);
+ }
+
+ private void reset() {
+ eventCount = 0;
+ modified.clear();
+ deleted.clear();
+ }
+
+ public void assertNoEvent() {
+ assertEquals("Unexpected WorkingTreeModifiedEvent ", 0, eventCount);
+ }
+
+ public void assertEvent(String[] expectedModified,
+ String[] expectedDeleted) {
+ String[] actuallyModified = getModified();
+ String[] actuallyDeleted = getDeleted();
+ Arrays.sort(actuallyModified);
+ Arrays.sort(expectedModified);
+ Arrays.sort(actuallyDeleted);
+ Arrays.sort(expectedDeleted);
+ assertArrayEquals("Unexpected modifications reported", expectedModified,
+ actuallyModified);
+ assertArrayEquals("Unexpected deletions reported", expectedDeleted,
+ actuallyDeleted);
+ reset();
+ }
+}
diff --git a/org.eclipse.jgit.test/tests.bzl b/org.eclipse.jgit.test/tests.bzl
index 8ae0065..c75ab76 100644
--- a/org.eclipse.jgit.test/tests.bzl
+++ b/org.eclipse.jgit.test/tests.bzl
@@ -29,6 +29,14 @@
additional_deps = [
"//org.eclipse.jgit:insecure_cipher_factory",
]
+ if src.endswith("OpenSshConfigTest.java"):
+ additional_deps = [
+ "//lib:jsch",
+ ]
+ if src.endswith("JschConfigSessionFactoryTest.java"):
+ additional_deps = [
+ "//lib:jsch",
+ ]
junit_tests(
name = name,
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/attributes/merge/disabled_checked.gif b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/attributes/merge/disabled_checked.gif
new file mode 100644
index 0000000..47b9e32
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/attributes/merge/disabled_checked.gif
Binary files differ
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/attributes/merge/enabled_checked.gif b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/attributes/merge/enabled_checked.gif
new file mode 100644
index 0000000..252f762
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/attributes/merge/enabled_checked.gif
Binary files differ
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
index ed3907e..aafda01 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
@@ -303,6 +303,21 @@ public void testAttributesWithTreeWalkFilter()
}
@Test
+ public void testAttributesConflictingMatch() throws Exception {
+ writeTrashFile(".gitattributes", "foo/** crlf=input\n*.jar binary");
+ writeTrashFile("foo/bar.jar", "\r\n");
+ // We end up with attributes [binary -diff -merge -text crlf=input].
+ // crlf should have no effect when -text is present.
+ try (Git git = new Git(db)) {
+ git.add().addFilepattern(".").call();
+ assertEquals(
+ "[.gitattributes, mode:100644, content:foo/** crlf=input\n*.jar binary]"
+ + "[foo/bar.jar, mode:100644, content:\r\n]",
+ indexState(CONTENT));
+ }
+ }
+
+ @Test
public void testCleanFilterEnvironment()
throws IOException, GitAPIException {
writeTrashFile(".gitattributes", "*.txt filter=tstFilter");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
index 3c19672..1201d9f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
@@ -85,7 +85,6 @@
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.FileUtils;
@@ -431,8 +430,8 @@ private Repository createRepositoryWithRemote() throws IOException,
config.save();
// fetch from first repository
- RefSpec spec = new RefSpec("+refs/heads/*:refs/remotes/origin/*");
- git2.fetch().setRemote("origin").setRefSpecs(spec).call();
+ git2.fetch().setRemote("origin")
+ .setRefSpecs("+refs/heads/*:refs/remotes/origin/*").call();
return db2;
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
index ae0b8dd..e687a6c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
@@ -76,6 +76,7 @@
import org.eclipse.jgit.submodule.SubmoduleWalk;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteConfig;
+import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.SystemReader;
import org.junit.Test;
@@ -145,16 +146,36 @@ public void testCloneRepositoryExplicitGitDir() throws IOException,
File directory = createTempDirectory("testCloneRepository");
CloneCommand command = Git.cloneRepository();
command.setDirectory(directory);
- command.setGitDir(new File(directory, ".git"));
+ command.setGitDir(new File(directory, Constants.DOT_GIT));
command.setURI(fileUri());
Git git2 = command.call();
addRepoToClose(git2.getRepository());
assertEquals(directory, git2.getRepository().getWorkTree());
- assertEquals(new File(directory, ".git"), git2.getRepository()
+ assertEquals(new File(directory, Constants.DOT_GIT), git2.getRepository()
.getDirectory());
}
@Test
+ public void testCloneRepositoryDefaultDirectory()
+ throws URISyntaxException, JGitInternalException {
+ CloneCommand command = Git.cloneRepository().setURI(fileUri());
+
+ command.verifyDirectories(new URIish(fileUri()));
+ File directory = command.getDirectory();
+ assertEquals(git.getRepository().getWorkTree().getName(), directory.getName());
+ }
+
+ @Test
+ public void testCloneBareRepositoryDefaultDirectory()
+ throws URISyntaxException, JGitInternalException {
+ CloneCommand command = Git.cloneRepository().setURI(fileUri()).setBare(true);
+
+ command.verifyDirectories(new URIish(fileUri()));
+ File directory = command.getDirectory();
+ assertEquals(git.getRepository().getWorkTree().getName() + Constants.DOT_GIT_EXT, directory.getName());
+ }
+
+ @Test
public void testCloneRepositoryExplicitGitDirNonStd() throws IOException,
JGitInternalException, GitAPIException {
File directory = createTempDirectory("testCloneRepository");
@@ -168,8 +189,8 @@ public void testCloneRepositoryExplicitGitDirNonStd() throws IOException,
assertEquals(directory, git2.getRepository().getWorkTree());
assertEquals(gDir, git2.getRepository()
.getDirectory());
- assertTrue(new File(directory, ".git").isFile());
- assertFalse(new File(gDir, ".git").exists());
+ assertTrue(new File(directory, Constants.DOT_GIT).isFile());
+ assertFalse(new File(gDir, Constants.DOT_GIT).exists());
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
index 37fee40..a0834e7 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
@@ -557,6 +557,11 @@ public void commitEmptyCommits() throws Exception {
} catch (EmtpyCommitException e) {
// expect this exception
}
+
+ // Allow empty commits also when setOnly was set
+ git.commit().setAuthor("New Author", "newauthor@example.org")
+ .setMessage("again no change").setOnly("file1")
+ .setAllowEmpty(true).call();
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
index 1e5d3bc..6a66783 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
@@ -54,6 +54,7 @@
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.RefNotFoundException;
+import org.eclipse.jgit.errors.InvalidPatternException;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
@@ -92,26 +93,65 @@ public void testDescribe() throws Exception {
ObjectId c1 = modify("aaa");
ObjectId c2 = modify("bbb");
- tag("t1");
+ tag("alice-t1");
ObjectId c3 = modify("ccc");
- tag("t2");
+ tag("bob-t2");
ObjectId c4 = modify("ddd");
assertNull(describe(c1));
assertNull(describe(c1, true));
- assertEquals("t1", describe(c2));
- assertEquals("t2", describe(c3));
- assertEquals("t2-0-g44579eb", describe(c3, true));
+ assertNull(describe(c1, "a*", "b*", "c*"));
+
+ assertEquals("alice-t1", describe(c2));
+ assertEquals("alice-t1", describe(c2, "alice*"));
+ assertNull(describe(c2, "bob*"));
+ assertNull(describe(c2, "?ob*"));
+ assertEquals("alice-t1", describe(c2, "a*", "b*", "c*"));
+
+ assertEquals("bob-t2", describe(c3));
+ assertEquals("bob-t2-0-g44579eb", describe(c3, true));
+ assertEquals("alice-t1-1-g44579eb", describe(c3, "alice*"));
+ assertEquals("alice-t1-1-g44579eb", describe(c3, "a??c?-t*"));
+ assertEquals("bob-t2", describe(c3, "bob*"));
+ assertEquals("bob-t2", describe(c3, "?ob*"));
+ assertEquals("bob-t2", describe(c3, "a*", "b*", "c*"));
assertNameStartsWith(c4, "3e563c5");
// the value verified with git-describe(1)
- assertEquals("t2-1-g3e563c5", describe(c4));
- assertEquals("t2-1-g3e563c5", describe(c4, true));
+ assertEquals("bob-t2-1-g3e563c5", describe(c4));
+ assertEquals("bob-t2-1-g3e563c5", describe(c4, true));
+ assertEquals("alice-t1-2-g3e563c5", describe(c4, "alice*"));
+ assertEquals("bob-t2-1-g3e563c5", describe(c4, "bob*"));
+ assertEquals("bob-t2-1-g3e563c5", describe(c4, "a*", "b*", "c*"));
// test default target
- assertEquals("t2-1-g3e563c5", git.describe().call());
+ assertEquals("bob-t2-1-g3e563c5", git.describe().call());
+ }
+
+ @Test
+ public void testDescribeMultiMatch() throws Exception {
+ ObjectId c1 = modify("aaa");
+ tag("v1.0.0");
+ tag("v1.1.1");
+ ObjectId c2 = modify("bbb");
+
+ // Ensure that if we're interested in any tags, we get the first match as per Git behaviour
+ assertEquals("v1.0.0", describe(c1));
+ assertEquals("v1.0.0-1-g3747db3", describe(c2));
+
+ // Ensure that if we're only interested in one of multiple tags, we get the right match
+ assertEquals("v1.0.0", describe(c1, "v1.0*"));
+ assertEquals("v1.1.1", describe(c1, "v1.1*"));
+ assertEquals("v1.0.0-1-g3747db3", describe(c2, "v1.0*"));
+ assertEquals("v1.1.1-1-g3747db3", describe(c2, "v1.1*"));
+
+ // Ensure that ordering of match precedence is preserved as per Git behaviour
+ assertEquals("v1.0.0", describe(c1, "v1.0*", "v1.1*"));
+ assertEquals("v1.1.1", describe(c1, "v1.1*", "v1.0*"));
+ assertEquals("v1.0.0-1-g3747db3", describe(c2, "v1.0*", "v1.1*"));
+ assertEquals("v1.1.1-1-g3747db3", describe(c2, "v1.1*", "v1.0*"));
}
/**
@@ -271,6 +311,10 @@ private String describe(ObjectId c1) throws GitAPIException, IOException {
return describe(c1, false);
}
+ private String describe(ObjectId c1, String... patterns) throws GitAPIException, IOException, InvalidPatternException {
+ return git.describe().setTarget(c1).setMatch(patterns).call();
+ }
+
private static void assertNameStartsWith(ObjectId c4, String prefix) {
assertTrue(c4.name(), c4.name().startsWith(prefix));
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java
index a36f6c5..530fb1b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java
@@ -45,7 +45,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Constants;
@@ -93,9 +95,8 @@ public void testFetch() throws Exception {
RevCommit commit = remoteGit.commit().setMessage("initial commit").call();
Ref tagRef = remoteGit.tag().setName("tag").call();
- RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x");
- git.fetch().setRemote("test").setRefSpecs(spec)
- .call();
+ git.fetch().setRemote("test")
+ .setRefSpecs("refs/heads/master:refs/heads/x").call();
assertEquals(commit.getId(),
db.resolve(commit.getId().getName() + "^{commit}"));
@@ -104,12 +105,97 @@ public void testFetch() throws Exception {
}
@Test
+ public void fetchAddsBranches() throws Exception {
+ final String branch1 = "b1";
+ final String branch2 = "b2";
+ final String remoteBranch1 = "test/" + branch1;
+ final String remoteBranch2 = "test/" + branch2;
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef1 = remoteGit.branchCreate().setName(branch1).call();
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef2 = remoteGit.branchCreate().setName(branch2).call();
+
+ String spec = "refs/heads/*:refs/remotes/test/*";
+ git.fetch().setRemote("test").setRefSpecs(spec).call();
+ assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1));
+ assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2));
+ }
+
+ @Test
+ public void fetchDoesntDeleteBranches() throws Exception {
+ final String branch1 = "b1";
+ final String branch2 = "b2";
+ final String remoteBranch1 = "test/" + branch1;
+ final String remoteBranch2 = "test/" + branch2;
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef1 = remoteGit.branchCreate().setName(branch1).call();
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef2 = remoteGit.branchCreate().setName(branch2).call();
+
+ String spec = "refs/heads/*:refs/remotes/test/*";
+ git.fetch().setRemote("test").setRefSpecs(spec).call();
+ assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1));
+ assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2));
+
+ remoteGit.branchDelete().setBranchNames(branch1).call();
+ git.fetch().setRemote("test").setRefSpecs(spec).call();
+ assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1));
+ assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2));
+ }
+
+ @Test
+ public void fetchUpdatesBranches() throws Exception {
+ final String branch1 = "b1";
+ final String branch2 = "b2";
+ final String remoteBranch1 = "test/" + branch1;
+ final String remoteBranch2 = "test/" + branch2;
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef1 = remoteGit.branchCreate().setName(branch1).call();
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef2 = remoteGit.branchCreate().setName(branch2).call();
+
+ String spec = "refs/heads/*:refs/remotes/test/*";
+ git.fetch().setRemote("test").setRefSpecs(spec).call();
+ assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1));
+ assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2));
+
+ remoteGit.commit().setMessage("commit").call();
+ branchRef2 = remoteGit.branchCreate().setName(branch2).setForce(true).call();
+ git.fetch().setRemote("test").setRefSpecs(spec).call();
+ assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1));
+ assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2));
+ }
+
+ @Test
+ public void fetchPrunesBranches() throws Exception {
+ final String branch1 = "b1";
+ final String branch2 = "b2";
+ final String remoteBranch1 = "test/" + branch1;
+ final String remoteBranch2 = "test/" + branch2;
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef1 = remoteGit.branchCreate().setName(branch1).call();
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef2 = remoteGit.branchCreate().setName(branch2).call();
+
+ String spec = "refs/heads/*:refs/remotes/test/*";
+ git.fetch().setRemote("test").setRefSpecs(spec).call();
+ assertEquals(branchRef1.getObjectId(), db.resolve(remoteBranch1));
+ assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2));
+
+ remoteGit.branchDelete().setBranchNames(branch1).call();
+ git.fetch().setRemote("test").setRefSpecs(spec)
+ .setRemoveDeletedRefs(true).call();
+ assertNull(db.resolve(remoteBranch1));
+ assertEquals(branchRef2.getObjectId(), db.resolve(remoteBranch2));
+ }
+
+ @Test
public void fetchShouldAutoFollowTag() throws Exception {
remoteGit.commit().setMessage("commit").call();
Ref tagRef = remoteGit.tag().setName("foo").call();
- RefSpec spec = new RefSpec("refs/heads/*:refs/remotes/origin/*");
- git.fetch().setRemote("test").setRefSpecs(spec)
+ git.fetch().setRemote("test")
+ .setRefSpecs("refs/heads/*:refs/remotes/origin/*")
.setTagOpt(TagOpt.AUTO_FOLLOW).call();
assertEquals(tagRef.getObjectId(), db.resolve("foo"));
@@ -120,8 +206,8 @@ public void fetchShouldAutoFollowTagForFetchedObjects() throws Exception {
remoteGit.commit().setMessage("commit").call();
Ref tagRef = remoteGit.tag().setName("foo").call();
remoteGit.commit().setMessage("commit2").call();
- RefSpec spec = new RefSpec("refs/heads/*:refs/remotes/origin/*");
- git.fetch().setRemote("test").setRefSpecs(spec)
+ git.fetch().setRemote("test")
+ .setRefSpecs("refs/heads/*:refs/remotes/origin/*")
.setTagOpt(TagOpt.AUTO_FOLLOW).call();
assertEquals(tagRef.getObjectId(), db.resolve("foo"));
}
@@ -132,9 +218,8 @@ public void fetchShouldNotFetchTagsFromOtherBranches() throws Exception {
remoteGit.checkout().setName("other").setCreateBranch(true).call();
remoteGit.commit().setMessage("commit2").call();
remoteGit.tag().setName("foo").call();
- RefSpec spec = new RefSpec(
- "refs/heads/master:refs/remotes/origin/master");
- git.fetch().setRemote("test").setRefSpecs(spec)
+ git.fetch().setRemote("test")
+ .setRefSpecs("refs/heads/master:refs/remotes/origin/master")
.setTagOpt(TagOpt.AUTO_FOLLOW).call();
assertNull(db.resolve("foo"));
}
@@ -146,7 +231,7 @@ public void fetchWithUpdatedTagShouldNotTryToUpdateLocal() throws Exception {
Ref tagRef = remoteGit.tag().setName(tagName).call();
ObjectId originalId = tagRef.getObjectId();
- RefSpec spec = new RefSpec("refs/heads/*:refs/remotes/origin/*");
+ String spec = "refs/heads/*:refs/remotes/origin/*";
git.fetch().setRemote("test").setRefSpecs(spec)
.setTagOpt(TagOpt.AUTO_FOLLOW).call();
assertEquals(originalId, db.resolve(tagName));
@@ -172,7 +257,7 @@ public void fetchWithExplicitTagsShouldUpdateLocal() throws Exception {
remoteGit.commit().setMessage("commit").call();
Ref tagRef1 = remoteGit.tag().setName(tagName).call();
- RefSpec spec = new RefSpec("refs/heads/*:refs/remotes/origin/*");
+ String spec = "refs/heads/*:refs/remotes/origin/*";
git.fetch().setRemote("test").setRefSpecs(spec)
.setTagOpt(TagOpt.AUTO_FOLLOW).call();
assertEquals(tagRef1.getObjectId(), db.resolve(tagName));
@@ -188,4 +273,75 @@ public void fetchWithExplicitTagsShouldUpdateLocal() throws Exception {
assertEquals(RefUpdate.Result.FORCED, update.getResult());
assertEquals(tagRef2.getObjectId(), db.resolve(tagName));
}
+
+ @Test
+ public void fetchAddRefsWithDuplicateRefspec() throws Exception {
+ final String branchName = "branch";
+ final String remoteBranchName = "test/" + branchName;
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef = remoteGit.branchCreate().setName(branchName).call();
+
+ final String spec1 = "+refs/heads/*:refs/remotes/test/*";
+ final String spec2 = "refs/heads/*:refs/remotes/test/*";
+ final StoredConfig config = db.getConfig();
+ RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+ remoteConfig.addFetchRefSpec(new RefSpec(spec1));
+ remoteConfig.addFetchRefSpec(new RefSpec(spec2));
+ remoteConfig.update(config);
+
+ git.fetch().setRemote("test").setRefSpecs(spec1).call();
+ assertEquals(branchRef.getObjectId(), db.resolve(remoteBranchName));
+ }
+
+ @Test
+ public void fetchPruneRefsWithDuplicateRefspec()
+ throws Exception {
+ final String branchName = "branch";
+ final String remoteBranchName = "test/" + branchName;
+ remoteGit.commit().setMessage("commit").call();
+ Ref branchRef = remoteGit.branchCreate().setName(branchName).call();
+
+ final String spec1 = "+refs/heads/*:refs/remotes/test/*";
+ final String spec2 = "refs/heads/*:refs/remotes/test/*";
+ final StoredConfig config = db.getConfig();
+ RemoteConfig remoteConfig = new RemoteConfig(config, "test");
+ remoteConfig.addFetchRefSpec(new RefSpec(spec1));
+ remoteConfig.addFetchRefSpec(new RefSpec(spec2));
+ remoteConfig.update(config);
+
+ git.fetch().setRemote("test").setRefSpecs(spec1).call();
+ assertEquals(branchRef.getObjectId(), db.resolve(remoteBranchName));
+
+ remoteGit.branchDelete().setBranchNames(branchName).call();
+ git.fetch().setRemote("test").setRefSpecs(spec1)
+ .setRemoveDeletedRefs(true).call();
+ assertNull(db.resolve(remoteBranchName));
+ }
+
+ @Test
+ public void fetchUpdateRefsWithDuplicateRefspec() throws Exception {
+ final String tagName = "foo";
+ remoteGit.commit().setMessage("commit").call();
+ Ref tagRef1 = remoteGit.tag().setName(tagName).call();
+ List<RefSpec> refSpecs = new ArrayList<>();
+ refSpecs.add(new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+ refSpecs.add(new RefSpec("+refs/tags/*:refs/tags/*"));
+ // Updating tags via the RefSpecs and setting TagOpt.FETCH_TAGS (or
+ // AUTO_FOLLOW) will result internally in *two* updates for the same
+ // ref.
+ git.fetch().setRemote("test").setRefSpecs(refSpecs)
+ .setTagOpt(TagOpt.AUTO_FOLLOW).call();
+ assertEquals(tagRef1.getObjectId(), db.resolve(tagName));
+
+ remoteGit.commit().setMessage("commit 2").call();
+ Ref tagRef2 = remoteGit.tag().setName(tagName).setForceUpdate(true)
+ .call();
+ FetchResult result = git.fetch().setRemote("test").setRefSpecs(refSpecs)
+ .setTagOpt(TagOpt.FETCH_TAGS).call();
+ assertEquals(2, result.getTrackingRefUpdates().size());
+ TrackingRefUpdate update = result
+ .getTrackingRefUpdate(Constants.R_TAGS + tagName);
+ assertEquals(RefUpdate.Result.FORCED, update.getResult());
+ assertEquals(tagRef2.getObjectId(), db.resolve(tagName));
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java
index 38178bf..bd0efad 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java
@@ -289,4 +289,4 @@ private void setCommitsAndMerge() throws Exception {
.setMessage("merge s0 with m1").call();
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
index 823516b..a341284 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
@@ -620,4 +620,4 @@ private static void assertFileContentsEqual(File actFile, String string)
fis.close();
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
index 8c613ec..e0c1499 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
@@ -83,6 +83,11 @@ public void testPush() throws JGitInternalException, IOException,
// create other repository
Repository db2 = createWorkRepository();
+ final StoredConfig config2 = db2.getConfig();
+
+ // this tests that this config can be parsed properly
+ config2.setString("fsck", "", "missingEmail", "ignore");
+ config2.save();
// setup the first repository
final StoredConfig config = db.getConfig();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
index f2e4d5b..ad3ab7f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
@@ -55,12 +55,15 @@
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.StashApplyFailureException;
+import org.eclipse.jgit.events.ChangeRecorder;
+import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.FileUtils;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -77,15 +80,31 @@ public class StashApplyCommandTest extends RepositoryTestCase {
private File committedFile;
+ private ChangeRecorder recorder;
+
+ private ListenerHandle handle;
+
@Override
@Before
public void setUp() throws Exception {
super.setUp();
git = Git.wrap(db);
+ recorder = new ChangeRecorder();
+ handle = db.getListenerList().addWorkingTreeModifiedListener(recorder);
committedFile = writeTrashFile(PATH, "content");
git.add().addFilepattern(PATH).call();
head = git.commit().setMessage("add file").call();
assertNotNull(head);
+ recorder.assertNoEvent();
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ if (handle != null) {
+ handle.remove();
+ }
+ super.tearDown();
}
@Test
@@ -95,10 +114,12 @@ public void workingDirectoryDelete() throws Exception {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertEquals("content", read(committedFile));
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertFalse(committedFile.exists());
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { PATH });
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -121,11 +142,13 @@ public void indexAdd() throws Exception {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertFalse(addedFile.exists());
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { addedPath });
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertTrue(addedFile.exists());
assertEquals("content2", read(addedFile));
+ recorder.assertEvent(new String[] { addedPath }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getChanged().isEmpty());
@@ -142,14 +165,17 @@ public void indexAdd() throws Exception {
@Test
public void indexDelete() throws Exception {
git.rm().addFilepattern("file.txt").call();
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "file.txt" });
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertEquals("content", read(committedFile));
+ recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertFalse(committedFile.exists());
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "file.txt" });
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -170,10 +196,12 @@ public void workingDirectoryModify() throws Exception {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertEquals("content", read(committedFile));
+ recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertEquals("content2", read(committedFile));
+ recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -193,16 +221,21 @@ public void workingDirectoryModifyInSubfolder() throws Exception {
File subfolderFile = writeTrashFile(path, "content");
git.add().addFilepattern(path).call();
head = git.commit().setMessage("add file").call();
+ recorder.assertNoEvent();
writeTrashFile(path, "content2");
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertEquals("content", read(subfolderFile));
+ recorder.assertEvent(new String[] { "d1/d2/f.txt" },
+ ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertEquals("content2", read(subfolderFile));
+ recorder.assertEvent(new String[] { "d1/d2/f.txt", "d1/d2", "d1" },
+ ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -225,10 +258,12 @@ public void workingDirectoryModifyIndexChanged() throws Exception {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertEquals("content", read(committedFile));
+ recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertEquals("content3", read(committedFile));
+ recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -252,10 +287,12 @@ public void workingDirectoryCleanIndexModify() throws Exception {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertEquals("content", read(committedFile));
+ recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertEquals("content2", read(committedFile));
+ recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -281,10 +318,12 @@ public void workingDirectoryDeleteIndexAdd() throws Exception {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertFalse(added.exists());
+ recorder.assertNoEvent();
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertEquals("content2", read(added));
+ recorder.assertEvent(new String[] { path }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getChanged().isEmpty());
@@ -308,10 +347,12 @@ public void workingDirectoryDeleteIndexEdit() throws Exception {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertEquals("content", read(committedFile));
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
assertFalse(committedFile.exists());
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { PATH });
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -337,9 +378,13 @@ public void multipleEdits() throws Exception {
assertNotNull(stashed);
assertTrue(committedFile.exists());
assertFalse(addedFile.exists());
+ recorder.assertEvent(new String[] { PATH },
+ new String[] { "file2.txt" });
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
+ recorder.assertEvent(new String[] { "file2.txt" },
+ new String[] { PATH });
Status status = git.status().call();
assertTrue(status.getChanged().isEmpty());
@@ -362,6 +407,7 @@ public void workingDirectoryContentConflict() throws Exception {
assertNotNull(stashed);
assertEquals("content", read(committedFile));
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "content3");
@@ -372,6 +418,7 @@ public void workingDirectoryContentConflict() throws Exception {
// expected
}
assertEquals("content3", read(PATH));
+ recorder.assertNoEvent();
}
@Test
@@ -391,10 +438,12 @@ public void stashedContentMerge() throws Exception {
assertEquals("content\nhead change\nmore content\n",
read(committedFile));
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "content\nmore content\ncommitted change\n");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("committed change").call();
+ recorder.assertNoEvent();
try {
git.stashApply().call();
@@ -402,6 +451,7 @@ public void stashedContentMerge() throws Exception {
} catch (StashApplyFailureException e) {
// expected
}
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
Status status = new StatusCommand(db).call();
assertEquals(1, status.getConflicting().size());
assertEquals(
@@ -426,12 +476,15 @@ public void stashedApplyOnOtherBranch() throws Exception {
writeTrashFile(PATH, "master content");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("even content").call();
+ recorder.assertNoEvent();
git.checkout().setName(otherBranch).call();
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "otherBranch content");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("even more content").call();
+ recorder.assertNoEvent();
writeTrashFile(path2, "content\nstashed change\nmore content\n");
@@ -442,12 +495,15 @@ public void stashedApplyOnOtherBranch() throws Exception {
assertEquals("otherBranch content",
read(committedFile));
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
git.checkout().setName("master").call();
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
git.stashApply().call();
assertEquals("content\nstashed change\nmore content\n", read(file2));
assertEquals("master content",
read(committedFile));
+ recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
}
@Test
@@ -467,12 +523,15 @@ public void stashedApplyOnOtherBranchWithStagedChange() throws Exception {
writeTrashFile(PATH, "master content");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("even content").call();
+ recorder.assertNoEvent();
git.checkout().setName(otherBranch).call();
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "otherBranch content");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("even more content").call();
+ recorder.assertNoEvent();
writeTrashFile(path2,
"content\nstashed change in index\nmore content\n");
@@ -485,8 +544,10 @@ public void stashedApplyOnOtherBranchWithStagedChange() throws Exception {
assertEquals("content\nmore content\n", read(file2));
assertEquals("otherBranch content", read(committedFile));
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
git.checkout().setName("master").call();
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
git.stashApply().call();
assertEquals("content\nstashed change\nmore content\n", read(file2));
assertEquals(
@@ -494,6 +555,7 @@ public void stashedApplyOnOtherBranchWithStagedChange() throws Exception {
+ "[file2.txt, mode:100644, content:content\nstashed change in index\nmore content\n]",
indexState(CONTENT));
assertEquals("master content", read(committedFile));
+ recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
}
@Test
@@ -501,6 +563,7 @@ public void workingDirectoryContentMerge() throws Exception {
writeTrashFile(PATH, "content\nmore content\n");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("more content").call();
+ recorder.assertNoEvent();
writeTrashFile(PATH, "content\nstashed change\nmore content\n");
@@ -508,15 +571,18 @@ public void workingDirectoryContentMerge() throws Exception {
assertNotNull(stashed);
assertEquals("content\nmore content\n", read(committedFile));
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "content\nmore content\ncommitted change\n");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("committed change").call();
+ recorder.assertNoEvent();
git.stashApply().call();
assertEquals(
"content\nstashed change\nmore content\ncommitted change\n",
read(committedFile));
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
}
@Test
@@ -527,6 +593,7 @@ public void indexContentConflict() throws Exception {
assertNotNull(stashed);
assertEquals("content", read(committedFile));
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "content3");
git.add().addFilepattern(PATH).call();
@@ -538,6 +605,7 @@ public void indexContentConflict() throws Exception {
} catch (StashApplyFailureException e) {
// expected
}
+ recorder.assertNoEvent();
assertEquals("content2", read(PATH));
}
@@ -549,6 +617,7 @@ public void workingDirectoryEditPreCommit() throws Exception {
assertNotNull(stashed);
assertEquals("content", read(committedFile));
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
String path2 = "file2.txt";
writeTrashFile(path2, "content3");
@@ -557,6 +626,7 @@ public void workingDirectoryEditPreCommit() throws Exception {
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getAdded().isEmpty());
@@ -583,12 +653,15 @@ public void stashChangeInANewSubdirectory() throws Exception {
RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed);
assertTrue(git.status().call().isClean());
+ recorder.assertEvent(ChangeRecorder.EMPTY,
+ new String[] { subdir, path });
git.branchCreate().setName(otherBranch).call();
git.checkout().setName(otherBranch).call();
ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed);
+ recorder.assertEvent(new String[] { path }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertTrue(status.getChanged().isEmpty());
@@ -643,12 +716,15 @@ public void testApplyStashWithDeletedFile() throws Exception {
git.commit().setMessage("x").call();
file.delete();
git.rm().addFilepattern("file").call();
+ recorder.assertNoEvent();
git.stashCreate().call();
+ recorder.assertEvent(new String[] { "file" }, ChangeRecorder.EMPTY);
file.delete();
git.stashApply().setStashRef("stash@{0}").call();
assertFalse(file.exists());
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "file" });
}
@Test
@@ -660,9 +736,11 @@ public void untrackedFileNotIncluded() throws Exception {
git.add().addFilepattern(PATH).call();
git.stashCreate().call();
assertTrue(untrackedFile.exists());
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
git.stashApply().setStashRef("stash@{0}").call();
assertTrue(untrackedFile.exists());
+ recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertEquals(1, status.getUntracked().size());
@@ -684,11 +762,14 @@ public void untrackedFileIncluded() throws Exception {
.call();
assertNotNull(stashedCommit);
assertFalse(untrackedFile.exists());
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { path });
+
deleteTrashFile("a/b"); // checkout should create parent dirs
git.stashApply().setStashRef("stash@{0}").call();
assertTrue(untrackedFile.exists());
assertEquals("content", read(path));
+ recorder.assertEvent(new String[] { path }, ChangeRecorder.EMPTY);
Status status = git.status().call();
assertEquals(1, status.getUntracked().size());
@@ -706,6 +787,7 @@ public void untrackedFileConflictsWithCommit() throws Exception {
String path = "untracked.txt";
writeTrashFile(path, "untracked");
git.stashCreate().setIncludeUntracked(true).call();
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { path });
writeTrashFile(path, "committed");
head = git.commit().setMessage("add file").call();
@@ -719,6 +801,7 @@ public void untrackedFileConflictsWithCommit() throws Exception {
assertEquals(e.getMessage(), JGitText.get().stashApplyConflict);
}
assertEquals("committed", read(path));
+ recorder.assertNoEvent();
}
@Test
@@ -727,6 +810,7 @@ public void untrackedFileConflictsWithWorkingDirectory()
String path = "untracked.txt";
writeTrashFile(path, "untracked");
git.stashCreate().setIncludeUntracked(true).call();
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { path });
writeTrashFile(path, "working-directory");
try {
@@ -736,6 +820,7 @@ public void untrackedFileConflictsWithWorkingDirectory()
assertEquals(e.getMessage(), JGitText.get().stashApplyConflict);
}
assertEquals("working-directory", read(path));
+ recorder.assertNoEvent();
}
@Test
@@ -747,11 +832,13 @@ public void untrackedAndTrackedChanges() throws Exception {
assertTrue(PATH + " should exist", check(PATH));
assertEquals(PATH + " should have been reset", "content", read(PATH));
assertFalse(path + " should not exist", check(path));
+ recorder.assertEvent(new String[] { PATH }, new String[] { path });
git.stashApply().setStashRef("stash@{0}").call();
assertTrue(PATH + " should exist", check(PATH));
assertEquals(PATH + " should have new content", "changed", read(PATH));
assertTrue(path + " should exist", check(path));
assertEquals(path + " should have new content", "untracked",
read(path));
+ recorder.assertEvent(new String[] { PATH, path }, ChangeRecorder.EMPTY);
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashListCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashListCommandTest.java
index a7e0ab9..d658a53 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashListCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashListCommandTest.java
@@ -51,6 +51,7 @@
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -94,9 +95,7 @@ public void singleStashedCommit() throws Exception {
git.add().addFilepattern("file.txt").call();
RevCommit commit = git.commit().setMessage("create file").call();
- RefUpdate update = db.updateRef(Constants.R_STASH);
- update.setNewObjectId(commit);
- assertEquals(Result.NEW, update.update());
+ assertEquals(Result.NEW, newStashUpdate(commit).update());
StashListCommand command = git.stashList();
Collection<RevCommit> stashed = command.call();
@@ -117,13 +116,8 @@ public void multipleStashedCommits() throws Exception {
git.add().addFilepattern("file.txt").call();
RevCommit commit2 = git.commit().setMessage("edit file").call();
- RefUpdate create = db.updateRef(Constants.R_STASH);
- create.setNewObjectId(commit1);
- assertEquals(Result.NEW, create.update());
-
- RefUpdate update = db.updateRef(Constants.R_STASH);
- update.setNewObjectId(commit2);
- assertEquals(Result.FAST_FORWARD, update.update());
+ assertEquals(Result.NEW, newStashUpdate(commit1).update());
+ assertEquals(Result.FAST_FORWARD, newStashUpdate(commit2).update());
StashListCommand command = git.stashList();
Collection<RevCommit> stashed = command.call();
@@ -133,4 +127,11 @@ public void multipleStashedCommits() throws Exception {
assertEquals(commit2, iter.next());
assertEquals(commit1, iter.next());
}
+
+ private RefUpdate newStashUpdate(ObjectId newId) throws Exception {
+ RefUpdate ru = db.updateRef(Constants.R_STASH);
+ ru.setNewObjectId(newId);
+ ru.setForceRefLog(true);
+ return ru;
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java
index ca456b3..5868482 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java
@@ -1,4 +1,7 @@
/*
+ * Copyright (C) 2015, 2017 Ivan Motsch <ivan.motsch@bsiag.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
@@ -254,6 +257,282 @@ public void testCyclicMacros() throws Exception {
endWalk();
}
+ @Test
+ public void testRelativePaths() throws Exception {
+ setupRepo("sub/ global", "sub/** init",
+ "sub/** top_sub\n*.txt top",
+ "sub/** subsub\nsub/ subsub2\n*.txt foo");
+ // The last sub/** is in sub/.gitattributes. It must not
+ // apply to any of the files here. It would match for a
+ // further subdirectory sub/sub. The sub/ rules must match
+ // only for directories.
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "sub", attrs("global"));
+ assertIteration(F, "sub/.gitattributes", attrs("init top_sub"));
+ assertIteration(F, "sub/a.txt", attrs("init foo top top_sub"));
+ endWalk();
+ // All right, let's see that they *do* apply in sub/sub:
+ writeTrashFile("sub/sub/b.txt", "b");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "sub", attrs("global"));
+ assertIteration(F, "sub/.gitattributes", attrs("init top_sub"));
+ assertIteration(F, "sub/a.txt", attrs("init foo top top_sub"));
+ assertIteration(D, "sub/sub", attrs("init subsub2 top_sub global"));
+ assertIteration(F, "sub/sub/b.txt",
+ attrs("init foo subsub top top_sub"));
+ endWalk();
+ }
+
+ @Test
+ public void testNestedMatchNot() throws Exception {
+ setupRepo(null, null, "*.xml xml\n*.jar jar", null);
+ writeTrashFile("foo.xml/bar.jar", "b");
+ writeTrashFile("foo.xml/bar.xml", "bx");
+ writeTrashFile("sub/b.jar", "bj");
+ writeTrashFile("sub/b.xml", "bx");
+ // On foo.xml/bar.jar we must not have 'xml'
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo.xml", attrs("xml"));
+ assertIteration(F, "foo.xml/bar.jar", attrs("jar"));
+ assertIteration(F, "foo.xml/bar.xml", attrs("xml"));
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(F, "sub/b.jar", attrs("jar"));
+ assertIteration(F, "sub/b.xml", attrs("xml"));
+ endWalk();
+ }
+
+ @Test
+ public void testNestedMatch() throws Exception {
+ // See also CGitAttributeTest.testNestedMatch()
+ setupRepo(null, null, "foo/ xml\nsub/foo/ sub\n*.jar jar", null);
+ writeTrashFile("foo/bar.jar", "b");
+ writeTrashFile("foo/bar.xml", "bx");
+ writeTrashFile("sub/b.jar", "bj");
+ writeTrashFile("sub/b.xml", "bx");
+ writeTrashFile("sub/foo/b.jar", "bf");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo", attrs("xml"));
+ assertIteration(F, "foo/bar.jar", attrs("jar"));
+ assertIteration(F, "foo/bar.xml");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(F, "sub/b.jar", attrs("jar"));
+ assertIteration(F, "sub/b.xml");
+ assertIteration(D, "sub/foo", attrs("sub xml"));
+ assertIteration(F, "sub/foo/b.jar", attrs("jar"));
+ endWalk();
+ }
+
+ @Test
+ public void testNestedMatchRecursive() throws Exception {
+ setupRepo(null, null, "foo/** xml\n*.jar jar", null);
+ writeTrashFile("foo/bar.jar", "b");
+ writeTrashFile("foo/bar.xml", "bx");
+ writeTrashFile("sub/b.jar", "bj");
+ writeTrashFile("sub/b.xml", "bx");
+ writeTrashFile("sub/foo/b.jar", "bf");
+ // On foo.xml/bar.jar we must not have 'xml'
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(F, "foo/bar.jar", attrs("jar xml"));
+ assertIteration(F, "foo/bar.xml", attrs("xml"));
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(F, "sub/b.jar", attrs("jar"));
+ assertIteration(F, "sub/b.xml");
+ assertIteration(D, "sub/foo");
+ assertIteration(F, "sub/foo/b.jar", attrs("jar"));
+ endWalk();
+ }
+
+ @Test
+ public void testStarMatchOnSlashNot() throws Exception {
+ setupRepo(null, null, "s*xt bar", null);
+ writeTrashFile("sub/a.txt", "1");
+ writeTrashFile("foo/sext", "2");
+ writeTrashFile("foo/s.txt", "3");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(F, "foo/s.txt", attrs("bar"));
+ assertIteration(F, "foo/sext", attrs("bar"));
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testPrefixMatchNot() throws Exception {
+ setupRepo(null, null, "sub/new bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testComplexPathMatch() throws Exception {
+ setupRepo(null, null, "s[t-v]b/n[de]w bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("sub/ndw", "2");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(F, "sub/ndw", attrs("bar"));
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testStarPathMatch() throws Exception {
+ setupRepo(null, null, "sub/new/* bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("sub/new/lower/foo.txt", "2");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new");
+ assertIteration(F, "sub/new/foo.txt", attrs("bar"));
+ assertIteration(D, "sub/new/lower", attrs("bar"));
+ assertIteration(F, "sub/new/lower/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testDirectoryMatchSubSimple() throws Exception {
+ setupRepo(null, null, "sub/new/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ writeTrashFile("sub/sub/new/foo.txt", "3");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(D, "foo/sub");
+ assertIteration(D, "foo/sub/new");
+ assertIteration(F, "foo/sub/new/foo.txt");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ assertIteration(D, "sub/sub");
+ assertIteration(D, "sub/sub/new");
+ assertIteration(F, "sub/sub/new/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursive() throws Exception {
+ setupRepo(null, null, "**/sub/new/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(D, "foo/sub");
+ assertIteration(D, "foo/sub/new", attrs("bar"));
+ assertIteration(F, "foo/sub/new/foo.txt");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack() throws Exception {
+ setupRepo(null, null, "**/sub/new/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ writeTrashFile("sub/sub/new/foo.txt", "3");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(D, "foo/sub");
+ assertIteration(D, "foo/sub/new", attrs("bar"));
+ assertIteration(F, "foo/sub/new/foo.txt");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ assertIteration(D, "sub/sub");
+ assertIteration(D, "sub/sub/new", attrs("bar"));
+ assertIteration(F, "sub/sub/new/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack2() throws Exception {
+ setupRepo(null, null, "**/**/sub/new/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ writeTrashFile("sub/sub/new/foo.txt", "3");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(D, "foo/sub");
+ assertIteration(D, "foo/sub/new", attrs("bar"));
+ assertIteration(F, "foo/sub/new/foo.txt");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ assertIteration(D, "sub/sub");
+ assertIteration(D, "sub/sub/new", attrs("bar"));
+ assertIteration(F, "sub/sub/new/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testDirectoryMatchSubComplex() throws Exception {
+ setupRepo(null, null, "s[uv]b/n*/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(D, "foo/sub");
+ assertIteration(D, "foo/sub/new");
+ assertIteration(F, "foo/sub/new/foo.txt");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ endWalk();
+ }
+
+ @Test
+ public void testDirectoryMatch() throws Exception {
+ setupRepo(null, null, "new/ bar", null);
+ writeTrashFile("sub/new/foo.txt", "1");
+ writeTrashFile("foo/sub/new/foo.txt", "2");
+ writeTrashFile("foo/new", "3");
+ walk = beginWalk();
+ assertIteration(F, ".gitattributes");
+ assertIteration(D, "foo");
+ assertIteration(F, "foo/new");
+ assertIteration(D, "foo/sub");
+ assertIteration(D, "foo/sub/new", attrs("bar"));
+ assertIteration(F, "foo/sub/new/foo.txt");
+ assertIteration(D, "sub");
+ assertIteration(F, "sub/a.txt");
+ assertIteration(D, "sub/new", attrs("bar"));
+ assertIteration(F, "sub/new/foo.txt");
+ endWalk();
+ }
+
private static Collection<Attribute> attrs(String s) {
return new AttributesRule("*", s).getAttributes();
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java
index e8dd952..72cc1d1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java
@@ -109,16 +109,16 @@ public void testFileNameWildcards() {
pattern = "/src/ne?";
assertMatched(pattern, "/src/new/");
assertMatched(pattern, "/src/new");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/src/new/a/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new.c");
//Test name-only fnmatcher matches
pattern = "ne?";
assertMatched(pattern, "/src/new/");
assertMatched(pattern, "/src/new");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/src/new/a/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/new/a/a.c");
assertMatched(pattern, "/neb");
assertNotMatched(pattern, "/src/new.c");
}
@@ -169,16 +169,16 @@ public void testTargetWithoutLeadingSlash() {
pattern = "/src/ne?";
assertMatched(pattern, "src/new/");
assertMatched(pattern, "src/new");
- assertMatched(pattern, "src/new/a.c");
- assertMatched(pattern, "src/new/a/a.c");
+ assertNotMatched(pattern, "src/new/a.c");
+ assertNotMatched(pattern, "src/new/a/a.c");
assertNotMatched(pattern, "src/new.c");
//Test name-only fnmatcher matches
pattern = "ne?";
assertMatched(pattern, "src/new/");
assertMatched(pattern, "src/new");
- assertMatched(pattern, "src/new/a.c");
- assertMatched(pattern, "src/new/a/a.c");
+ assertNotMatched(pattern, "src/new/a.c");
+ assertNotMatched(pattern, "src/new/a/a.c");
assertMatched(pattern, "neb");
assertNotMatched(pattern, "src/new.c");
}
@@ -197,35 +197,50 @@ public void testParentDirectoryGitAttributes() {
pattern = "/src/new";
assertMatched(pattern, "/src/new/");
assertMatched(pattern, "/src/new");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/src/new/a/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new.c");
//Test child directory is matched, slash after name
pattern = "/src/new/";
assertMatched(pattern, "/src/new/");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/src/new/a/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/new/a/a.c");
assertNotMatched(pattern, "/src/new");
assertNotMatched(pattern, "/src/new.c");
//Test directory is matched by name only
pattern = "b1";
- assertMatched(pattern, "/src/new/a/b1/a.c");
+ assertNotMatched(pattern, "/src/new/a/b1/a.c");
assertNotMatched(pattern, "/src/new/a/b2/file.c");
assertNotMatched(pattern, "/src/new/a/bb1/file.c");
assertNotMatched(pattern, "/src/new/a/file.c");
+ assertNotMatched(pattern, "/src/new/a/bb1");
+ assertMatched(pattern, "/src/new/a/b1");
}
@Test
public void testTrailingSlash() {
String pattern = "/src/";
assertMatched(pattern, "/src/");
- assertMatched(pattern, "/src/new");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "/src/new");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/a.c");
assertNotMatched(pattern, "/src");
assertNotMatched(pattern, "/srcA/");
+
+ pattern = "src/";
+ assertMatched(pattern, "src/");
+ assertMatched(pattern, "/src/");
+ assertNotMatched(pattern, "src");
+ assertNotMatched(pattern, "/src/new");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "foo/src/a.c");
+ assertNotMatched(pattern, "foo/src/bar/a.c");
+ assertNotMatched(pattern, "foo/src/bar/src");
+ assertMatched(pattern, "foo/src/");
+ assertMatched(pattern, "foo/src/bar/src/");
}
@Test
@@ -239,51 +254,58 @@ public void testNameOnlyMatches() {
assertMatched(pattern, "/src/test.stp");
assertNotMatched(pattern, "/test.stp1");
assertNotMatched(pattern, "/test.astp");
+ assertNotMatched(pattern, "test.stp/foo.bar");
+ assertMatched(pattern, "test.stp");
+ assertMatched(pattern, "test.stp/");
+ assertMatched(pattern, "test.stp/test.stp");
//Test matches for name-only, applies to file name or folder name
pattern = "src";
assertMatched(pattern, "/src");
assertMatched(pattern, "/src/");
- assertMatched(pattern, "/src/a.c");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/new/src/a.c");
+ assertNotMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/new/src/a.c");
assertMatched(pattern, "/file/src");
//Test matches for name-only, applies only to folder names
pattern = "src/";
- assertMatched(pattern, "/src/");
- assertMatched(pattern, "/src/a.c");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/new/src/a.c");
+ assertNotMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/new/src/a.c");
assertNotMatched(pattern, "/src");
assertNotMatched(pattern, "/file/src");
+ assertMatched(pattern, "/file/src/");
//Test matches for name-only, applies to file name or folder name
//With a small wildcard
pattern = "?rc";
- assertMatched(pattern, "/src/a.c");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/new/src/a.c");
+ assertNotMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/new/src/a.c");
+ assertMatched(pattern, "/new/src/");
assertMatched(pattern, "/file/src");
assertMatched(pattern, "/src/");
//Test matches for name-only, applies to file name or folder name
//With a small wildcard
pattern = "?r[a-c]";
- assertMatched(pattern, "/src/a.c");
- assertMatched(pattern, "/src/new/a.c");
- assertMatched(pattern, "/new/src/a.c");
+ assertNotMatched(pattern, "/src/a.c");
+ assertNotMatched(pattern, "/src/new/a.c");
+ assertNotMatched(pattern, "/new/src/a.c");
assertMatched(pattern, "/file/src");
assertMatched(pattern, "/src/");
- assertMatched(pattern, "/srb/a.c");
- assertMatched(pattern, "/grb/new/a.c");
- assertMatched(pattern, "/new/crb/a.c");
+ assertNotMatched(pattern, "/srb/a.c");
+ assertNotMatched(pattern, "/grb/new/a.c");
+ assertNotMatched(pattern, "/new/crb/a.c");
assertMatched(pattern, "/file/3rb");
assertMatched(pattern, "/xrb/");
- assertMatched(pattern, "/3ra/a.c");
- assertMatched(pattern, "/5ra/new/a.c");
- assertMatched(pattern, "/new/1ra/a.c");
+ assertNotMatched(pattern, "/3ra/a.c");
+ assertNotMatched(pattern, "/5ra/new/a.c");
+ assertNotMatched(pattern, "/new/1ra/a.c");
+ assertNotMatched(pattern, "/new/1ra/a.c/");
assertMatched(pattern, "/file/dra");
+ assertMatched(pattern, "/file/dra/");
assertMatched(pattern, "/era/");
assertNotMatched(pattern, "/crg");
assertNotMatched(pattern, "/cr3");
@@ -360,6 +382,39 @@ public void testGetters() {
assertEquals(r.getAttributes().get(2).toString(), "attribute3=value");
}
+ @Test
+ public void testBracketsInGroup() {
+ //combinations of brackets in brackets, escaped and not
+
+ String[] patterns = new String[]{"[[\\]]", "[\\[\\]]"};
+ for (String pattern : patterns) {
+ assertNotMatched(pattern, "");
+ assertNotMatched(pattern, "[]");
+ assertNotMatched(pattern, "][");
+ assertNotMatched(pattern, "[\\[]");
+ assertNotMatched(pattern, "[[]");
+ assertNotMatched(pattern, "[[]]");
+ assertNotMatched(pattern, "[\\[\\]]");
+
+ assertMatched(pattern, "[");
+ assertMatched(pattern, "]");
+ }
+
+ patterns = new String[]{"[[]]", "[\\[]]"};
+ for (String pattern : patterns) {
+ assertNotMatched(pattern, "");
+ assertMatched(pattern, "[]");
+ assertNotMatched(pattern, "][");
+ assertNotMatched(pattern, "[\\[]");
+ assertNotMatched(pattern, "[[]");
+ assertNotMatched(pattern, "[[]]");
+ assertNotMatched(pattern, "[\\[\\]]");
+
+ assertNotMatched(pattern, "[");
+ assertNotMatched(pattern, "]");
+ }
+ }
+
/**
* Check for a match. If target ends with "/", match will assume that the
* target is meant to be a directory.
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
index ec2370e..f0d3c36 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
@@ -166,6 +166,25 @@ public void testTabSeparator() throws IOException {
assertAttribute("file.type3", node, asSet(A_UNSET_ATTR, B_SET_ATTR));
}
+ @Test
+ public void testDoubleAsteriskAtEnd() throws IOException {
+ String attributeFileContent = "dir/** \tA -B\tC=value";
+
+ is = new ByteArrayInputStream(attributeFileContent.getBytes());
+ AttributesNode node = new AttributesNode();
+ node.parse(is);
+ assertAttribute("dir", node,
+ asSet(new Attribute[]{}));
+ assertAttribute("dir/", node,
+ asSet(new Attribute[]{}));
+ assertAttribute("dir/file.type1", node,
+ asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR));
+ assertAttribute("dir/sub/", node,
+ asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR));
+ assertAttribute("dir/sub/file.type1", node,
+ asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR));
+ }
+
private void assertAttribute(String path, AttributesNode node,
Attributes attrs) throws IOException {
Attributes attributes = new Attributes();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java
new file mode 100644
index 0000000..3483813
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/CGitAttributesTest.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2017 Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.attributes;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.ExecutionResult;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.TemporaryBuffer;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests that verify that the attributes of files in a repository are the same
+ * in JGit and in C-git.
+ */
+public class CGitAttributesTest extends RepositoryTestCase {
+
+ @Before
+ public void initRepo() throws IOException {
+ // Because we run C-git, we must ensure that global or user exclude
+ // files cannot influence the tests. So we set core.excludesFile to an
+ // empty file inside the repository.
+ StoredConfig config = db.getConfig();
+ File fakeUserGitignore = writeTrashFile(".fake_user_gitignore", "");
+ config.setString("core", null, "excludesFile",
+ fakeUserGitignore.getAbsolutePath());
+ // Disable case-insensitivity -- JGit doesn't handle that yet.
+ config.setBoolean("core", null, "ignoreCase", false);
+ // And try to switch off the global attributes file, too.
+ config.setString("core", null, "attributesFile",
+ fakeUserGitignore.getAbsolutePath());
+ config.save();
+ }
+
+ private void createFiles(String... paths) throws IOException {
+ for (String path : paths) {
+ writeTrashFile(path, "x");
+ }
+ }
+
+ private String toString(TemporaryBuffer b) throws IOException {
+ return RawParseUtils.decode(b.toByteArray());
+ }
+
+ private Attribute fromString(String key, String value) {
+ if ("set".equals(value)) {
+ return new Attribute(key, Attribute.State.SET);
+ }
+ if ("unset".equals(value)) {
+ return new Attribute(key, Attribute.State.UNSET);
+ }
+ if ("unspecified".equals(value)) {
+ return new Attribute(key, Attribute.State.UNSPECIFIED);
+ }
+ return new Attribute(key, value);
+ }
+
+ private LinkedHashMap<String, Attributes> cgitAttributes(
+ Set<String> allFiles) throws Exception {
+ FS fs = db.getFS();
+ StringBuilder input = new StringBuilder();
+ for (String filename : allFiles) {
+ input.append(filename).append('\n');
+ }
+ ProcessBuilder builder = fs.runInShell("git",
+ new String[] { "check-attr", "--stdin", "--all" });
+ builder.directory(db.getWorkTree());
+ builder.environment().put("HOME", fs.userHome().getAbsolutePath());
+ ExecutionResult result = fs.execute(builder, new ByteArrayInputStream(
+ input.toString().getBytes(Constants.CHARSET)));
+ String errorOut = toString(result.getStderr());
+ assertEquals("External git failed", "exit 0\n",
+ "exit " + result.getRc() + '\n' + errorOut);
+ LinkedHashMap<String, Attributes> map = new LinkedHashMap<>();
+ try (BufferedReader r = new BufferedReader(new InputStreamReader(
+ new BufferedInputStream(result.getStdout().openInputStream()),
+ Constants.CHARSET))) {
+ r.lines().forEach(line -> {
+ // Parse the line and add to result map
+ int start = 0;
+ int i = line.indexOf(':');
+ String path = line.substring(0, i).trim();
+ start = i + 1;
+ i = line.indexOf(':', start);
+ String key = line.substring(start, i).trim();
+ String value = line.substring(i + 1).trim();
+ Attribute attr = fromString(key, value);
+ Attributes attrs = map.get(path);
+ if (attrs == null) {
+ attrs = new Attributes(attr);
+ map.put(path, attrs);
+ } else {
+ attrs.put(attr);
+ }
+ });
+ }
+ return map;
+ }
+
+ private LinkedHashMap<String, Attributes> jgitAttributes()
+ throws IOException {
+ // Do a tree walk and return a list of all files and directories with
+ // their attributes
+ LinkedHashMap<String, Attributes> result = new LinkedHashMap<>();
+ try (TreeWalk walk = new TreeWalk(db)) {
+ walk.addTree(new FileTreeIterator(db));
+ walk.setFilter(new NotIgnoredFilter(0));
+ while (walk.next()) {
+ String path = walk.getPathString();
+ if (walk.isSubtree() && !path.endsWith("/")) {
+ // git check-attr expects directory paths to end with a
+ // slash
+ path += '/';
+ }
+ Attributes attrs = walk.getAttributes();
+ if (attrs != null && !attrs.isEmpty()) {
+ result.put(path, attrs);
+ } else {
+ result.put(path, null);
+ }
+ if (walk.isSubtree()) {
+ walk.enterSubtree();
+ }
+ }
+ }
+ return result;
+ }
+
+ private void assertSameAsCGit() throws Exception {
+ LinkedHashMap<String, Attributes> jgit = jgitAttributes();
+ LinkedHashMap<String, Attributes> cgit = cgitAttributes(jgit.keySet());
+ // remove all without attributes
+ Iterator<Map.Entry<String, Attributes>> iterator = jgit.entrySet()
+ .iterator();
+ while (iterator.hasNext()) {
+ Map.Entry<String, Attributes> entry = iterator.next();
+ if (entry.getValue() == null) {
+ iterator.remove();
+ }
+ }
+ assertArrayEquals("JGit attributes differ from C git",
+ cgit.entrySet().toArray(), jgit.entrySet().toArray());
+ }
+
+ @Test
+ public void testBug508568() throws Exception {
+ createFiles("foo.xml/bar.jar", "sub/foo.xml/bar.jar");
+ writeTrashFile(".gitattributes", "*.xml xml\n" + "*.jar jar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testRelativePath() throws Exception {
+ createFiles("sub/foo.txt");
+ writeTrashFile("sub/.gitattributes", "sub/** sub\n" + "*.txt txt\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testRelativePaths() throws Exception {
+ createFiles("sub/foo.txt", "sub/sub/bar", "foo/sub/a.txt",
+ "foo/sub/bar/a.tmp");
+ writeTrashFile(".gitattributes", "sub/** sub\n" + "*.txt txt\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testNestedMatchNot() throws Exception {
+ createFiles("foo.xml/bar.jar", "foo.xml/bar.xml", "sub/b.jar",
+ "sub/b.xml");
+ writeTrashFile("sub/.gitattributes", "*.xml xml\n" + "*.jar jar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testNestedMatch() throws Exception {
+ // This is an interesting test. At the time of this writing, the
+ // gitignore documentation says: "In other words, foo/ will match a
+ // directory foo AND PATHS UNDERNEATH IT, but will not match a regular
+ // file or a symbolic link foo". (Emphasis added.) And gitattributes is
+ // supposed to follow the same rules. But the documentation appears to
+ // lie: C-git will *not* apply the attribute "xml" to *any* files in
+ // any subfolder "foo" here. It will only apply the "jar" attribute
+ // to the three *.jar files.
+ //
+ // The point is probably that ignores are handled top-down, and once a
+ // directory "foo" is matched (here: on paths "foo" and "sub/foo" by
+ // pattern "foo/"), the directory is excluded and the gitignore
+ // documentation also says: "It is not possible to re-include a file if
+ // a parent directory of that file is excluded." So once the pattern
+ // "foo/" has matched, it appears as if everything beneath would also be
+ // matched.
+ //
+ // But not so for gitattributes! The foo/ rule only matches the
+ // directory itself, but not anything beneath.
+ createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml",
+ "sub/foo/b.jar");
+ writeTrashFile(".gitattributes",
+ "foo/ xml\n" + "sub/foo/ sub\n" + "*.jar jar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testNestedMatchWithWildcard() throws Exception {
+ // See above.
+ createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml",
+ "sub/foo/b.jar");
+ writeTrashFile(".gitattributes",
+ "**/foo/ xml\n" + "*/foo/ sub\n" + "*.jar jar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testNestedMatchRecursive() throws Exception {
+ createFiles("foo/bar.jar", "foo/bar.xml", "sub/b.jar", "sub/b.xml",
+ "sub/foo/b.jar");
+ writeTrashFile(".gitattributes", "foo/** xml\n" + "*.jar jar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testStarMatchOnSlashNot() throws Exception {
+ createFiles("sub/a.txt", "foo/sext", "foo/s.txt");
+ writeTrashFile(".gitattributes", "s*xt bar");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testPrefixMatchNot() throws Exception {
+ createFiles("src/new/foo.txt");
+ writeTrashFile(".gitattributes", "src/new bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testComplexPathMatchNot() throws Exception {
+ createFiles("src/new/foo.txt", "src/ndw");
+ writeTrashFile(".gitattributes", "s[p-s]c/n[de]w bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testStarPathMatchNot() throws Exception {
+ createFiles("src/new/foo.txt", "src/ndw");
+ writeTrashFile(".gitattributes", "src/* bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubSimple() throws Exception {
+ createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
+ writeTrashFile(".gitattributes", "src/new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursive() throws Exception {
+ createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
+ writeTrashFile(".gitattributes", "**/src/new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack() throws Exception {
+ createFiles("src/new/foo.txt", "src/src/new/foo.txt");
+ writeTrashFile(".gitattributes", "**/src/new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack2() throws Exception {
+ createFiles("src/new/foo.txt", "src/src/new/foo.txt");
+ writeTrashFile(".gitattributes", "**/**/src/new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack3() throws Exception {
+ createFiles("src/new/src/new/foo.txt",
+ "foo/src/new/bar/src/new/foo.txt");
+ writeTrashFile(".gitattributes", "**/src/new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack4() throws Exception {
+ createFiles("src/src/src/new/foo.txt",
+ "foo/src/src/bar/src/new/foo.txt");
+ writeTrashFile(".gitattributes", "**/src/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack5() throws Exception {
+ createFiles("x/a/a/b/foo.txt", "x/y/z/b/a/b/foo.txt",
+ "x/y/a/a/a/a/b/foo.txt", "x/y/a/a/a/a/b/a/b/foo.txt");
+ writeTrashFile(".gitattributes", "**/*/a/b bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack6() throws Exception {
+ createFiles("x/a/a/b/foo.txt", "x/y/a/b/a/b/foo.txt");
+ writeTrashFile(".gitattributes", "**/*/**/a/b bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubComplex() throws Exception {
+ createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
+ writeTrashFile(".gitattributes", "s[rs]c/n*/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatch() throws Exception {
+ createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
+ writeTrashFile(".gitattributes", "new/ bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testBracketsInGroup() throws Exception {
+ createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]");
+ writeTrashFile(".gitattributes", "[[]] bar1\n" + "[\\[]] bar2\n"
+ + "[[\\]] bar3\n" + "[\\[\\]] bar4\n");
+ assertSameAsCGit();
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java
new file mode 100644
index 0000000..a4f3d18
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/merge/MergeGitAttributeTest.java
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr)
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.attributes.merge;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.function.Consumer;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.MergeResult;
+import org.eclipse.jgit.api.MergeResult.MergeStatus;
+import org.eclipse.jgit.api.errors.CheckoutConflictException;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
+import org.eclipse.jgit.api.errors.NoFilepatternException;
+import org.eclipse.jgit.api.errors.NoHeadException;
+import org.eclipse.jgit.api.errors.NoMessageException;
+import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
+import org.eclipse.jgit.attributes.Attribute;
+import org.eclipse.jgit.attributes.Attributes;
+import org.eclipse.jgit.errors.NoWorkTreeException;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class MergeGitAttributeTest extends RepositoryTestCase {
+
+ private static final String REFS_HEADS_RIGHT = "refs/heads/right";
+
+ private static final String REFS_HEADS_MASTER = "refs/heads/master";
+
+ private static final String REFS_HEADS_LEFT = "refs/heads/left";
+
+ private static final String DISABLE_CHECK_BRANCH = "refs/heads/disabled_checked";
+
+ private static final String ENABLE_CHECKED_BRANCH = "refs/heads/enabled_checked";
+
+ private static final String ENABLED_CHECKED_GIF = "enabled_checked.gif";
+
+ public Git createRepositoryBinaryConflict(Consumer<Git> initialCommit,
+ Consumer<Git> leftCommit, Consumer<Git> rightCommit)
+ throws NoFilepatternException, GitAPIException, NoWorkTreeException,
+ IOException {
+ // Set up a git whith conflict commits on images
+ Git git = new Git(db);
+
+ // First commit
+ initialCommit.accept(git);
+ git.add().addFilepattern(".").call();
+ RevCommit firstCommit = git.commit().setAll(true)
+ .setMessage("initial commit adding git attribute file").call();
+
+ // Create branch and add an icon Checked_Boxe (enabled_checked)
+ createBranch(firstCommit, REFS_HEADS_LEFT);
+ checkoutBranch(REFS_HEADS_LEFT);
+ leftCommit.accept(git);
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("Left").call();
+
+ // Create a second branch from master Unchecked_Boxe
+ checkoutBranch(REFS_HEADS_MASTER);
+ createBranch(firstCommit, REFS_HEADS_RIGHT);
+ checkoutBranch(REFS_HEADS_RIGHT);
+ rightCommit.accept(git);
+ git.add().addFilepattern(".").call();
+ git.commit().setMessage("Right").call();
+
+ checkoutBranch(REFS_HEADS_LEFT);
+ return git;
+
+ }
+
+ @Test
+ public void mergeTextualFile_NoAttr() throws NoWorkTreeException,
+ NoFilepatternException, GitAPIException, IOException {
+ try (Git git = createRepositoryBinaryConflict(g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ })) {
+ checkoutBranch(REFS_HEADS_LEFT);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+
+ MergeResult mergeResult = git.merge()
+ .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
+ .call();
+ assertEquals(MergeStatus.MERGED, mergeResult.getMergeStatus());
+
+ assertNull(mergeResult.getConflicts());
+
+ // Check that the image was not modified (not conflict marker added)
+ String result = read(
+ writeTrashFile("res.cat", "A\n" + "E\n" + "C\n" + "F\n"));
+ assertEquals(result, read(git.getRepository().getWorkTree().toPath()
+ .resolve("main.cat").toFile()));
+ }
+ }
+
+ @Test
+ public void mergeTextualFile_UnsetMerge_Conflict()
+ throws NoWorkTreeException, NoFilepatternException, GitAPIException,
+ IOException {
+ try (Git git = createRepositoryBinaryConflict(g -> {
+ try {
+ writeTrashFile(".gitattributes", "*.cat -merge");
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ })) {
+ // Check that the merge attribute is unset
+ assertAddMergeAttributeUnset(REFS_HEADS_LEFT, "main.cat");
+ assertAddMergeAttributeUnset(REFS_HEADS_RIGHT, "main.cat");
+
+ checkoutBranch(REFS_HEADS_LEFT);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+
+ String catContent = read(git.getRepository().getWorkTree().toPath()
+ .resolve("main.cat").toFile());
+
+ MergeResult mergeResult = git.merge()
+ .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
+ .call();
+ assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (not conflict marker added)
+ assertEquals(catContent, read(git.getRepository().getWorkTree()
+ .toPath().resolve("main.cat").toFile()));
+ }
+ }
+
+ @Test
+ public void mergeTextualFile_UnsetMerge_NoConflict()
+ throws NoWorkTreeException, NoFilepatternException, GitAPIException,
+ IOException {
+ try (Git git = createRepositoryBinaryConflict(g -> {
+ try {
+ writeTrashFile(".gitattributes", "*.txt -merge");
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ })) {
+ // Check that the merge attribute is unset
+ assertAddMergeAttributeUndefined(REFS_HEADS_LEFT, "main.cat");
+ assertAddMergeAttributeUndefined(REFS_HEADS_RIGHT, "main.cat");
+
+ checkoutBranch(REFS_HEADS_LEFT);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+
+ MergeResult mergeResult = git.merge()
+ .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
+ .call();
+ assertEquals(MergeStatus.MERGED, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (not conflict marker added)
+ String result = read(
+ writeTrashFile("res.cat", "A\n" + "E\n" + "C\n" + "F\n"));
+ assertEquals(result, read(git.getRepository().getWorkTree()
+ .toPath().resolve("main.cat").toFile()));
+ }
+ }
+
+ @Test
+ public void mergeTextualFile_SetBinaryMerge_Conflict()
+ throws NoWorkTreeException, NoFilepatternException, GitAPIException,
+ IOException {
+ try (Git git = createRepositoryBinaryConflict(g -> {
+ try {
+ writeTrashFile(".gitattributes", "*.cat merge=binary");
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "B\n" + "C\n" + "F\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }, g -> {
+ try {
+ writeTrashFile("main.cat", "A\n" + "E\n" + "C\n" + "D\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ })) {
+ // Check that the merge attribute is set to binary
+ assertAddMergeAttributeCustom(REFS_HEADS_LEFT, "main.cat",
+ "binary");
+ assertAddMergeAttributeCustom(REFS_HEADS_RIGHT, "main.cat",
+ "binary");
+
+ checkoutBranch(REFS_HEADS_LEFT);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+
+ String catContent = read(git.getRepository().getWorkTree().toPath()
+ .resolve("main.cat").toFile());
+
+ MergeResult mergeResult = git.merge()
+ .include(git.getRepository().resolve(REFS_HEADS_RIGHT))
+ .call();
+ assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (not conflict marker added)
+ assertEquals(catContent, read(git.getRepository().getWorkTree()
+ .toPath().resolve("main.cat").toFile()));
+ }
+ }
+
+ /*
+ * This test is commented because JGit add conflict markers in binary files.
+ * cf. https://www.eclipse.org/forums/index.php/t/1086511/
+ */
+ @Test
+ @Ignore
+ public void mergeBinaryFile_NoAttr_Conflict() throws IllegalStateException,
+ IOException, NoHeadException, ConcurrentRefUpdateException,
+ CheckoutConflictException, InvalidMergeHeadsException,
+ WrongRepositoryStateException, NoMessageException, GitAPIException {
+
+ RevCommit disableCheckedCommit;
+ FileInputStream mergeResultFile = null;
+ // Set up a git with conflict commits on images
+ try (Git git = new Git(db)) {
+ // First commit
+ write(new File(db.getWorkTree(), ".gitattributes"), "");
+ git.add().addFilepattern(".gitattributes").call();
+ RevCommit firstCommit = git.commit()
+ .setMessage("initial commit adding git attribute file")
+ .call();
+
+ // Create branch and add an icon Checked_Boxe (enabled_checked)
+ createBranch(firstCommit, ENABLE_CHECKED_BRANCH);
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ git.commit().setMessage("enabled_checked commit").call();
+
+ // Create a second branch from master Unchecked_Boxe
+ checkoutBranch(REFS_HEADS_MASTER);
+ createBranch(firstCommit, DISABLE_CHECK_BRANCH);
+ checkoutBranch(DISABLE_CHECK_BRANCH);
+ copy("disabled_checked.gif", ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ disableCheckedCommit = git.commit()
+ .setMessage("disabled_checked commit").call();
+
+ // Check that the merge attribute is unset
+ assertAddMergeAttributeUndefined(ENABLE_CHECKED_BRANCH,
+ ENABLED_CHECKED_GIF);
+ assertAddMergeAttributeUndefined(DISABLE_CHECK_BRANCH,
+ ENABLED_CHECKED_GIF);
+
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+ MergeResult mergeResult = git.merge().include(disableCheckedCommit)
+ .call();
+ assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (no conflict marker added)
+ mergeResultFile = new FileInputStream(
+ db.getWorkTree().toPath().resolve(ENABLED_CHECKED_GIF)
+ .toFile());
+ assertTrue(contentEquals(
+ getClass().getResourceAsStream(ENABLED_CHECKED_GIF),
+ mergeResultFile));
+ } finally {
+ if (mergeResultFile != null) {
+ mergeResultFile.close();
+ }
+ }
+ }
+
+ @Test
+ public void mergeBinaryFile_UnsetMerge_Conflict()
+ throws IllegalStateException,
+ IOException, NoHeadException, ConcurrentRefUpdateException,
+ CheckoutConflictException, InvalidMergeHeadsException,
+ WrongRepositoryStateException, NoMessageException, GitAPIException {
+
+ RevCommit disableCheckedCommit;
+ FileInputStream mergeResultFile = null;
+ // Set up a git whith conflict commits on images
+ try (Git git = new Git(db)) {
+ // First commit
+ write(new File(db.getWorkTree(), ".gitattributes"), "*.gif -merge");
+ git.add().addFilepattern(".gitattributes").call();
+ RevCommit firstCommit = git.commit()
+ .setMessage("initial commit adding git attribute file")
+ .call();
+
+ // Create branch and add an icon Checked_Boxe (enabled_checked)
+ createBranch(firstCommit, ENABLE_CHECKED_BRANCH);
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ git.commit().setMessage("enabled_checked commit").call();
+
+ // Create a second branch from master Unchecked_Boxe
+ checkoutBranch(REFS_HEADS_MASTER);
+ createBranch(firstCommit, DISABLE_CHECK_BRANCH);
+ checkoutBranch(DISABLE_CHECK_BRANCH);
+ copy("disabled_checked.gif", ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ disableCheckedCommit = git.commit()
+ .setMessage("disabled_checked commit").call();
+
+ // Check that the merge attribute is unset
+ assertAddMergeAttributeUnset(ENABLE_CHECKED_BRANCH,
+ ENABLED_CHECKED_GIF);
+ assertAddMergeAttributeUnset(DISABLE_CHECK_BRANCH,
+ ENABLED_CHECKED_GIF);
+
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+ MergeResult mergeResult = git.merge().include(disableCheckedCommit)
+ .call();
+ assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (not conflict marker added)
+ mergeResultFile = new FileInputStream(db.getWorkTree().toPath()
+ .resolve(ENABLED_CHECKED_GIF).toFile());
+ assertTrue(contentEquals(
+ getClass().getResourceAsStream(ENABLED_CHECKED_GIF),
+ mergeResultFile));
+ } finally {
+ if (mergeResultFile != null) {
+ mergeResultFile.close();
+ }
+ }
+ }
+
+ @Test
+ public void mergeBinaryFile_SetMerge_Conflict()
+ throws IllegalStateException, IOException, NoHeadException,
+ ConcurrentRefUpdateException, CheckoutConflictException,
+ InvalidMergeHeadsException, WrongRepositoryStateException,
+ NoMessageException, GitAPIException {
+
+ RevCommit disableCheckedCommit;
+ FileInputStream mergeResultFile = null;
+ // Set up a git whith conflict commits on images
+ try (Git git = new Git(db)) {
+ // First commit
+ write(new File(db.getWorkTree(), ".gitattributes"), "*.gif merge");
+ git.add().addFilepattern(".gitattributes").call();
+ RevCommit firstCommit = git.commit()
+ .setMessage("initial commit adding git attribute file")
+ .call();
+
+ // Create branch and add an icon Checked_Boxe (enabled_checked)
+ createBranch(firstCommit, ENABLE_CHECKED_BRANCH);
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ copy(ENABLED_CHECKED_GIF, ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ git.commit().setMessage("enabled_checked commit").call();
+
+ // Create a second branch from master Unchecked_Boxe
+ checkoutBranch(REFS_HEADS_MASTER);
+ createBranch(firstCommit, DISABLE_CHECK_BRANCH);
+ checkoutBranch(DISABLE_CHECK_BRANCH);
+ copy("disabled_checked.gif", ENABLED_CHECKED_GIF, "");
+ git.add().addFilepattern(ENABLED_CHECKED_GIF).call();
+ disableCheckedCommit = git.commit()
+ .setMessage("disabled_checked commit").call();
+
+ // Check that the merge attribute is set
+ assertAddMergeAttributeSet(ENABLE_CHECKED_BRANCH,
+ ENABLED_CHECKED_GIF);
+ assertAddMergeAttributeSet(DISABLE_CHECK_BRANCH,
+ ENABLED_CHECKED_GIF);
+
+ checkoutBranch(ENABLE_CHECKED_BRANCH);
+ // Merge refs/heads/enabled_checked -> refs/heads/disabled_checked
+ MergeResult mergeResult = git.merge().include(disableCheckedCommit)
+ .call();
+ assertEquals(MergeStatus.CONFLICTING, mergeResult.getMergeStatus());
+
+ // Check that the image was not modified (not conflict marker added)
+ mergeResultFile = new FileInputStream(db.getWorkTree().toPath()
+ .resolve(ENABLED_CHECKED_GIF).toFile());
+ assertFalse(contentEquals(
+ getClass().getResourceAsStream(ENABLED_CHECKED_GIF),
+ mergeResultFile));
+ } finally {
+ if (mergeResultFile != null) {
+ mergeResultFile.close();
+ }
+ }
+ }
+
+ /*
+ * Copied from org.apache.commons.io.IOUtils
+ */
+ private boolean contentEquals(InputStream input1, InputStream input2)
+ throws IOException {
+ if (input1 == input2) {
+ return true;
+ }
+ if (!(input1 instanceof BufferedInputStream)) {
+ input1 = new BufferedInputStream(input1);
+ }
+ if (!(input2 instanceof BufferedInputStream)) {
+ input2 = new BufferedInputStream(input2);
+ }
+
+ int ch = input1.read();
+ while (-1 != ch) {
+ final int ch2 = input2.read();
+ if (ch != ch2) {
+ return false;
+ }
+ ch = input1.read();
+ }
+
+ final int ch2 = input2.read();
+ return ch2 == -1;
+ }
+
+ private void assertAddMergeAttributeUnset(String branch, String fileName)
+ throws IllegalStateException, IOException {
+ checkoutBranch(branch);
+
+ try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
+ treeWaklEnableChecked.addTree(new FileTreeIterator(db));
+ treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
+
+ assertTrue(treeWaklEnableChecked.next());
+ Attributes attributes = treeWaklEnableChecked.getAttributes();
+ Attribute mergeAttribute = attributes.get("merge");
+ assertNotNull(mergeAttribute);
+ assertEquals(Attribute.State.UNSET, mergeAttribute.getState());
+ }
+ }
+
+ private void assertAddMergeAttributeSet(String branch, String fileName)
+ throws IllegalStateException, IOException {
+ checkoutBranch(branch);
+
+ try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
+ treeWaklEnableChecked.addTree(new FileTreeIterator(db));
+ treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
+
+ assertTrue(treeWaklEnableChecked.next());
+ Attributes attributes = treeWaklEnableChecked.getAttributes();
+ Attribute mergeAttribute = attributes.get("merge");
+ assertNotNull(mergeAttribute);
+ assertEquals(Attribute.State.SET, mergeAttribute.getState());
+ }
+ }
+
+ private void assertAddMergeAttributeUndefined(String branch,
+ String fileName) throws IllegalStateException, IOException {
+ checkoutBranch(branch);
+
+ try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
+ treeWaklEnableChecked.addTree(new FileTreeIterator(db));
+ treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
+
+ assertTrue(treeWaklEnableChecked.next());
+ Attributes attributes = treeWaklEnableChecked.getAttributes();
+ Attribute mergeAttribute = attributes.get("merge");
+ assertNull(mergeAttribute);
+ }
+ }
+
+ private void assertAddMergeAttributeCustom(String branch, String fileName,
+ String value) throws IllegalStateException, IOException {
+ checkoutBranch(branch);
+
+ try (TreeWalk treeWaklEnableChecked = new TreeWalk(db)) {
+ treeWaklEnableChecked.addTree(new FileTreeIterator(db));
+ treeWaklEnableChecked.setFilter(PathFilter.create(fileName));
+
+ assertTrue(treeWaklEnableChecked.next());
+ Attributes attributes = treeWaklEnableChecked.getAttributes();
+ Attribute mergeAttribute = attributes.get("merge");
+ assertNotNull(mergeAttribute);
+ assertEquals(Attribute.State.CUSTOM, mergeAttribute.getState());
+ assertEquals(value, mergeAttribute.getValue());
+ }
+ }
+
+ private void copy(String resourcePath, String resourceNewName,
+ String pathInRepo) throws IOException {
+ InputStream input = getClass().getResourceAsStream(resourcePath);
+ Files.copy(input, db.getWorkTree().toPath().resolve(pathInRepo)
+ .resolve(resourceNewName));
+ }
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java
index 12f4dcc..341cc4f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java
@@ -47,6 +47,7 @@
import static org.junit.Assert.assertTrue;
import java.io.File;
+
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java
new file mode 100644
index 0000000..ee8191f
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2017 Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.ignore;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.WorkingTreeIterator;
+import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.ExecutionResult;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.TemporaryBuffer;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests that verify that the set of ignore files in a repository is the same in
+ * JGit and in C-git.
+ */
+public class CGitIgnoreTest extends RepositoryTestCase {
+
+ @Before
+ public void initRepo() throws IOException {
+ // These tests focus on .gitignore files inside the repository. Because
+ // we run C-git, we must ensure that global or user exclude files cannot
+ // influence the tests. So we set core.excludesFile to an empty file
+ // inside the repository.
+ File fakeUserGitignore = writeTrashFile(".fake_user_gitignore", "");
+ StoredConfig config = db.getConfig();
+ config.setString("core", null, "excludesFile",
+ fakeUserGitignore.getAbsolutePath());
+ // Disable case-insensitivity -- JGit doesn't handle that yet.
+ config.setBoolean("core", null, "ignoreCase", false);
+ config.save();
+ }
+
+ private void createFiles(String... paths) throws IOException {
+ for (String path : paths) {
+ writeTrashFile(path, "x");
+ }
+ }
+
+ private String toString(TemporaryBuffer b) throws IOException {
+ return RawParseUtils.decode(b.toByteArray());
+ }
+
+ private String[] cgitIgnored() throws Exception {
+ FS fs = db.getFS();
+ ProcessBuilder builder = fs.runInShell("git", new String[] { "ls-files",
+ "--ignored", "--exclude-standard", "-o" });
+ builder.directory(db.getWorkTree());
+ builder.environment().put("HOME", fs.userHome().getAbsolutePath());
+ ExecutionResult result = fs.execute(builder,
+ new ByteArrayInputStream(new byte[0]));
+ String errorOut = toString(result.getStderr());
+ assertEquals("External git failed", "exit 0\n",
+ "exit " + result.getRc() + '\n' + errorOut);
+ try (BufferedReader r = new BufferedReader(new InputStreamReader(
+ new BufferedInputStream(result.getStdout().openInputStream()),
+ Constants.CHARSET))) {
+ return r.lines().toArray(String[]::new);
+ }
+ }
+
+ private LinkedHashSet<String> jgitIgnored() throws IOException {
+ // Do a tree walk that does descend into ignored directories and return
+ // a list of all ignored files
+ LinkedHashSet<String> result = new LinkedHashSet<>();
+ try (TreeWalk walk = new TreeWalk(db)) {
+ walk.addTree(new FileTreeIterator(db));
+ walk.setRecursive(true);
+ while (walk.next()) {
+ if (walk.getTree(WorkingTreeIterator.class).isEntryIgnored()) {
+ result.add(walk.getPathString());
+ }
+ }
+ }
+ return result;
+ }
+
+ private void assertNoIgnoredVisited(Set<String> ignored) throws Exception {
+ // Do a recursive tree walk with a NotIgnoredFilter and verify that none
+ // of the files visited is in the ignored set
+ try (TreeWalk walk = new TreeWalk(db)) {
+ walk.addTree(new FileTreeIterator(db));
+ walk.setFilter(new NotIgnoredFilter(0));
+ walk.setRecursive(true);
+ while (walk.next()) {
+ String path = walk.getPathString();
+ assertFalse("File " + path + " is ignored, should not appear",
+ ignored.contains(path));
+ }
+ }
+ }
+
+ private void assertSameAsCGit(String... notIgnored) throws Exception {
+ LinkedHashSet<String> ignored = jgitIgnored();
+ String[] cgit = cgitIgnored();
+ assertArrayEquals(cgit, ignored.toArray());
+ for (String notExcluded : notIgnored) {
+ assertFalse("File " + notExcluded + " should not be ignored",
+ ignored.contains(notExcluded));
+ }
+ assertNoIgnoredVisited(ignored);
+ }
+
+ @Test
+ public void testSimpleIgnored() throws Exception {
+ createFiles("a.txt", "a.tmp", "src/sub/a.txt", "src/a.tmp",
+ "src/a.txt/b.tmp", "ignored/a.tmp", "ignored/not_ignored/a.tmp",
+ "ignored/other/a.tmp");
+ writeTrashFile(".gitignore",
+ "*.txt\n" + "/ignored/*\n" + "!/ignored/not_ignored");
+ assertSameAsCGit("ignored/not_ignored/a.tmp");
+ }
+
+ @Test
+ public void testDirOnlyMatch() throws Exception {
+ createFiles("a.txt", "src/foo/a.txt", "src/a.txt", "foo/a.txt");
+ writeTrashFile(".gitignore", "foo/");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirOnlyMatchDeep() throws Exception {
+ createFiles("a.txt", "src/foo/a.txt", "src/a.txt", "foo/a.txt");
+ writeTrashFile(".gitignore", "**/foo/");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testStarMatchOnSlashNot() throws Exception {
+ createFiles("sub/a.txt", "foo/sext", "foo/s.txt");
+ writeTrashFile(".gitignore", "s*xt");
+ assertSameAsCGit("sub/a.txt");
+ }
+
+ @Test
+ public void testPrefixMatch() throws Exception {
+ createFiles("src/new/foo.txt");
+ writeTrashFile(".gitignore", "src/new");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursive() throws Exception {
+ createFiles("src/new/foo.txt", "foo/src/new/foo.txt", "sub/src/new");
+ writeTrashFile(".gitignore", "**/src/new/");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack() throws Exception {
+ createFiles("src/new/foo.txt", "src/src/new/foo.txt");
+ writeTrashFile(".gitignore", "**/src/new/");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack2() throws Exception {
+ createFiles("src/new/foo.txt", "src/src/new/foo.txt");
+ writeTrashFile(".gitignore", "**/**/src/new/");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack3() throws Exception {
+ createFiles("x/a/a/b/foo.txt");
+ writeTrashFile(".gitignore", "**/*/a/b/");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack4() throws Exception {
+ createFiles("x/a/a/b/foo.txt", "x/y/z/b/a/b/foo.txt",
+ "x/y/a/a/a/a/b/foo.txt", "x/y/a/a/a/a/b/a/b/foo.txt");
+ writeTrashFile(".gitignore", "**/*/a/b bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testDirectoryMatchSubRecursiveBacktrack5() throws Exception {
+ createFiles("x/a/a/b/foo.txt", "x/y/a/b/a/b/foo.txt");
+ writeTrashFile(".gitignore", "**/*/**/a/b bar\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testUnescapedBracketsInGroup() throws Exception {
+ createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]");
+ writeTrashFile(".gitignore", "[[]]\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testEscapedFirstBracketInGroup() throws Exception {
+ createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]");
+ writeTrashFile(".gitignore", "[\\[]]\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testEscapedSecondBracketInGroup() throws Exception {
+ createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]");
+ writeTrashFile(".gitignore", "[[\\]]\n");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testEscapedBothBracketsInGroup() throws Exception {
+ createFiles("[", "]", "[]", "][", "[[]", "[]]", "[[]]");
+ writeTrashFile(".gitignore", "[\\[\\]]\n");
+ assertSameAsCGit();
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
index 1863b80..bcc8f7e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java
@@ -391,7 +391,6 @@ public void testWildmatch() {
assertMatched("/**/a/b", "c/d/a/b");
assertMatched("/**/**/a/b", "c/d/a/b");
- assertMatched("a/b/**", "a/b");
assertMatched("a/b/**", "a/b/c");
assertMatched("a/b/**", "a/b/c/d/");
assertMatched("a/b/**/**", "a/b/c/d");
@@ -415,6 +414,12 @@ public void testWildmatch() {
@Test
public void testWildmatchDoNotMatch() {
+ assertNotMatched("a/**", "a/");
+ assertNotMatched("a/b/**", "a/b/");
+ assertNotMatched("a/**", "a");
+ assertNotMatched("a/b/**", "a/b");
+ assertNotMatched("a/b/**/", "a/b");
+ assertNotMatched("a/b/**/**", "a/b");
assertNotMatched("**/a/b", "a/c/b");
assertNotMatched("!/**/*.zip", "c/a/b.zip");
assertNotMatched("!**/*.zip", "c/a/b.zip");
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/internal/StringsTest.java
similarity index 69%
copy from org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
copy to org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/internal/StringsTest.java
index 98a2a94..468989f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/internal/StringsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011, Google Inc.
+ * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -40,21 +40,34 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+package org.eclipse.jgit.ignore.internal;
-package org.eclipse.jgit.internal.storage.dfs;
+import static org.junit.Assert.assertEquals;
-import java.util.concurrent.atomic.AtomicLong;
+import org.junit.Test;
-final class DfsPackKey {
- final int hash;
+public class StringsTest {
- final AtomicLong cachedSize;
+ private void testString(String string, int n, int m) {
+ assertEquals(string, n, Strings.count(string, '/', false));
+ assertEquals(string, m, Strings.count(string, '/', true));
+ }
- DfsPackKey() {
- // Multiply by 31 here so we can more directly combine with another
- // value without doing the multiply there.
- //
- hash = System.identityHashCode(this) * 31;
- cachedSize = new AtomicLong();
+ @Test
+ public void testCount() {
+ testString("", 0, 0);
+ testString("/", 1, 0);
+ testString("//", 2, 0);
+ testString("///", 3, 1);
+ testString("////", 4, 2);
+ testString("foo", 0, 0);
+ testString("/foo", 1, 0);
+ testString("foo/", 1, 0);
+ testString("/foo/", 2, 0);
+ testString("foo/bar", 1, 1);
+ testString("/foo/bar/", 3, 1);
+ testString("/foo/bar//", 4, 2);
+ testString("/foo//bar/", 4, 2);
+ testString(" /foo/ ", 2, 2);
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java
index 4f3b601..4228c9d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java
@@ -142,7 +142,11 @@ private File restoreGitRepo(InputStream in, File testDir, String name)
String[] cmd = { "/bin/sh", "./" + name + ".sh" };
int exitCode;
String stdErr;
- Process process = Runtime.getRuntime().exec(cmd, null, testDir);
+ ProcessBuilder builder = new ProcessBuilder(cmd);
+ builder.environment().put("HOME",
+ FS.DETECTED.userHome().getAbsolutePath());
+ builder.directory(testDir);
+ Process process = builder.start();
try (InputStream stdOutStream = process.getInputStream();
InputStream stdErrStream = process.getErrorStream();
OutputStream stdInStream = process.getOutputStream()) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java
index 5bef9fa..32d711f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCacheTest.java
@@ -57,13 +57,14 @@
public class DeltaBaseCacheTest {
private static final int SZ = 512;
- private DfsPackKey key;
+ private DfsStreamKey key;
private DeltaBaseCache cache;
private TestRng rng;
@Before
public void setUp() {
- key = new DfsPackKey();
+ DfsRepositoryDescription repo = new DfsRepositoryDescription("test");
+ key = DfsStreamKey.of(repo, "test.key");
cache = new DeltaBaseCache(SZ);
rng = new TestRng(getClass().getSimpleName());
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java
new file mode 100644
index 0000000..2e3ee45
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.junit.TestRng;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class DfsBlockCacheTest {
+ @Rule
+ public TestName testName = new TestName();
+ private TestRng rng;
+ private DfsBlockCache cache;
+
+ @Before
+ public void setUp() {
+ rng = new TestRng(testName.getMethodName());
+ resetCache();
+ }
+
+ @SuppressWarnings("resource")
+ @Test
+ public void streamKeyReusesBlocks() throws Exception {
+ DfsRepositoryDescription repo = new DfsRepositoryDescription("test");
+ InMemoryRepository r1 = new InMemoryRepository(repo);
+ byte[] content = rng.nextBytes(424242);
+ ObjectId id;
+ try (ObjectInserter ins = r1.newObjectInserter()) {
+ id = ins.insert(OBJ_BLOB, content);
+ ins.flush();
+ }
+
+ long oldSize = cache.getCurrentSize();
+ assertTrue(oldSize > 2000);
+ assertEquals(0, cache.getHitCount());
+
+ List<DfsPackDescription> packs = r1.getObjectDatabase().listPacks();
+ InMemoryRepository r2 = new InMemoryRepository(repo);
+ r2.getObjectDatabase().commitPack(packs, Collections.emptyList());
+ try (ObjectReader rdr = r2.newObjectReader()) {
+ byte[] actual = rdr.open(id, OBJ_BLOB).getBytes();
+ assertTrue(Arrays.equals(content, actual));
+ }
+ assertEquals(0, cache.getMissCount());
+ assertEquals(oldSize, cache.getCurrentSize());
+ }
+
+ @SuppressWarnings("resource")
+ @Test
+ public void weirdBlockSize() throws Exception {
+ DfsRepositoryDescription repo = new DfsRepositoryDescription("test");
+ InMemoryRepository r1 = new InMemoryRepository(repo);
+
+ byte[] content1 = rng.nextBytes(4);
+ byte[] content2 = rng.nextBytes(424242);
+ ObjectId id1;
+ ObjectId id2;
+ try (ObjectInserter ins = r1.newObjectInserter()) {
+ id1 = ins.insert(OBJ_BLOB, content1);
+ id2 = ins.insert(OBJ_BLOB, content2);
+ ins.flush();
+ }
+
+ resetCache();
+ List<DfsPackDescription> packs = r1.getObjectDatabase().listPacks();
+
+ InMemoryRepository r2 = new InMemoryRepository(repo);
+ r2.getObjectDatabase().setReadableChannelBlockSizeForTest(500);
+ r2.getObjectDatabase().commitPack(packs, Collections.emptyList());
+ try (ObjectReader rdr = r2.newObjectReader()) {
+ byte[] actual = rdr.open(id1, OBJ_BLOB).getBytes();
+ assertTrue(Arrays.equals(content1, actual));
+ }
+
+ InMemoryRepository r3 = new InMemoryRepository(repo);
+ r3.getObjectDatabase().setReadableChannelBlockSizeForTest(500);
+ r3.getObjectDatabase().commitPack(packs, Collections.emptyList());
+ try (ObjectReader rdr = r3.newObjectReader()) {
+ byte[] actual = rdr.open(id2, OBJ_BLOB).getBytes();
+ assertTrue(Arrays.equals(content2, actual));
+ }
+ }
+
+ private void resetCache() {
+ DfsBlockCache.reconfigure(new DfsBlockCacheConfig()
+ .setBlockSize(512)
+ .setBlockLimit(1 << 20));
+ cache = DfsBlockCache.getInstance();
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java
new file mode 100644
index 0000000..804d744
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsFsckTest.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static org.eclipse.jgit.junit.JGitTestUtil.concat;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import static org.eclipse.jgit.lib.Constants.encodeASCII;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.internal.fsck.FsckError;
+import org.eclipse.jgit.internal.fsck.FsckError.CorruptObject;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectChecker.ErrorType;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DfsFsckTest {
+ private TestRepository<InMemoryRepository> git;
+
+ private InMemoryRepository repo;
+
+ private ObjectInserter ins;
+
+ @Before
+ public void setUp() throws IOException {
+ DfsRepositoryDescription desc = new DfsRepositoryDescription("test");
+ git = new TestRepository<>(new InMemoryRepository(desc));
+ repo = git.getRepository();
+ ins = repo.newObjectInserter();
+ }
+
+ @Test
+ public void testHealthyRepo() throws Exception {
+ RevCommit commit0 = git.commit().message("0").create();
+ RevCommit commit1 = git.commit().message("1").parent(commit0).create();
+ git.update("master", commit1);
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 0);
+ assertEquals(errors.getMissingObjects().size(), 0);
+ assertEquals(errors.getCorruptIndices().size(), 0);
+ }
+
+ @Test
+ public void testCommitWithCorruptAuthor() throws Exception {
+ StringBuilder b = new StringBuilder();
+ b.append("tree be9bfa841874ccc9f2ef7c48d0c76226f89b7189\n");
+ b.append("author b <b@c> <b@c> 0 +0000\n");
+ b.append("committer <> 0 +0000\n");
+ byte[] data = encodeASCII(b.toString());
+ ObjectId id = ins.insert(Constants.OBJ_COMMIT, data);
+ ins.flush();
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 1);
+ CorruptObject o = errors.getCorruptObjects().iterator().next();
+ assertTrue(o.getId().equals(id));
+ assertEquals(o.getErrorType(), ErrorType.BAD_DATE);
+ }
+
+ @Test
+ public void testCommitWithoutTree() throws Exception {
+ StringBuilder b = new StringBuilder();
+ b.append("parent ");
+ b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
+ b.append('\n');
+ byte[] data = encodeASCII(b.toString());
+ ObjectId id = ins.insert(Constants.OBJ_COMMIT, data);
+ ins.flush();
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 1);
+ CorruptObject o = errors.getCorruptObjects().iterator().next();
+ assertTrue(o.getId().equals(id));
+ assertEquals(o.getErrorType(), ErrorType.MISSING_TREE);
+ }
+
+ @Test
+ public void testTagWithoutObject() throws Exception {
+ StringBuilder b = new StringBuilder();
+ b.append("type commit\n");
+ b.append("tag test-tag\n");
+ b.append("tagger A. U. Thor <author@localhost> 1 +0000\n");
+ byte[] data = encodeASCII(b.toString());
+ ObjectId id = ins.insert(Constants.OBJ_TAG, data);
+ ins.flush();
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 1);
+ CorruptObject o = errors.getCorruptObjects().iterator().next();
+ assertTrue(o.getId().equals(id));
+ assertEquals(o.getErrorType(), ErrorType.MISSING_OBJECT);
+ }
+
+ @Test
+ public void testTreeWithNullSha() throws Exception {
+ byte[] data = concat(encodeASCII("100644 A"), new byte[] { '\0' },
+ new byte[OBJECT_ID_LENGTH]);
+ ObjectId id = ins.insert(Constants.OBJ_TREE, data);
+ ins.flush();
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 1);
+ CorruptObject o = errors.getCorruptObjects().iterator().next();
+ assertTrue(o.getId().equals(id));
+ assertEquals(o.getErrorType(), ErrorType.NULL_SHA1);
+ }
+
+ @Test
+ public void testMultipleInvalidObjects() throws Exception {
+ StringBuilder b = new StringBuilder();
+ b.append("tree ");
+ b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189");
+ b.append('\n');
+ b.append("parent ");
+ b.append("\n");
+ byte[] data = encodeASCII(b.toString());
+ ObjectId id1 = ins.insert(Constants.OBJ_COMMIT, data);
+
+ b = new StringBuilder();
+ b.append("100644");
+ data = encodeASCII(b.toString());
+ ObjectId id2 = ins.insert(Constants.OBJ_TREE, data);
+
+ ins.flush();
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+
+ assertEquals(errors.getCorruptObjects().size(), 2);
+ for (CorruptObject o : errors.getCorruptObjects()) {
+ if (o.getId().equals(id1)) {
+ assertEquals(o.getErrorType(), ErrorType.BAD_PARENT_SHA1);
+ } else if (o.getId().equals(id2)) {
+ assertNull(o.getErrorType());
+ } else {
+ fail();
+ }
+ }
+ }
+
+ @Test
+ public void testValidConnectivity() throws Exception {
+ ObjectId blobId = ins
+ .insert(Constants.OBJ_BLOB, Constants.encode("foo"));
+
+ byte[] blobIdBytes = new byte[OBJECT_ID_LENGTH];
+ blobId.copyRawTo(blobIdBytes, 0);
+ byte[] data = concat(encodeASCII("100644 regular-file\0"), blobIdBytes);
+ ObjectId treeId = ins.insert(Constants.OBJ_TREE, data);
+ ins.flush();
+
+ RevCommit commit = git.commit().message("0").setTopLevelTree(treeId)
+ .create();
+
+ git.update("master", commit);
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+ assertEquals(errors.getMissingObjects().size(), 0);
+ }
+
+ @Test
+ public void testMissingObject() throws Exception {
+ ObjectId blobId = ObjectId
+ .fromString("19102815663d23f8b75a47e7a01965dcdc96468c");
+ byte[] blobIdBytes = new byte[OBJECT_ID_LENGTH];
+ blobId.copyRawTo(blobIdBytes, 0);
+ byte[] data = concat(encodeASCII("100644 regular-file\0"), blobIdBytes);
+ ObjectId treeId = ins.insert(Constants.OBJ_TREE, data);
+ ins.flush();
+
+ RevCommit commit = git.commit().message("0").setTopLevelTree(treeId)
+ .create();
+
+ git.update("master", commit);
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+ assertEquals(errors.getMissingObjects().size(), 1);
+ assertEquals(errors.getMissingObjects().iterator().next(), blobId);
+ }
+
+ @Test
+ public void testNonCommitHead() throws Exception {
+ RevCommit commit0 = git.commit().message("0").create();
+ StringBuilder b = new StringBuilder();
+ b.append("object ");
+ b.append(commit0.getName());
+ b.append('\n');
+ b.append("type commit\n");
+ b.append("tag test-tag\n");
+ b.append("tagger A. U. Thor <author@localhost> 1 +0000\n");
+
+ byte[] data = encodeASCII(b.toString());
+ ObjectId tagId = ins.insert(Constants.OBJ_TAG, data);
+ ins.flush();
+
+ git.update("master", tagId);
+
+ DfsFsck fsck = new DfsFsck(repo);
+ FsckError errors = fsck.check(null);
+ assertEquals(errors.getCorruptObjects().size(), 0);
+ assertEquals(errors.getNonCommitHeads().size(), 1);
+ assertEquals(errors.getNonCommitHeads().iterator().next(),
+ "refs/heads/master");
+ }
+
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
index 17c1835..55a5f72 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
@@ -5,6 +5,7 @@
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.INSERT;
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -13,24 +14,35 @@
import static org.junit.Assert.fail;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
+import org.eclipse.jgit.internal.storage.reftable.RefCursor;
+import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
+import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
+import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
import org.eclipse.jgit.junit.MockSystemReader;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.pack.PackConfig;
+import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.util.SystemReader;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+/** Tests for pack creation and garbage expiration. */
public class DfsGarbageCollectorTest {
private TestRepository<InMemoryRepository> git;
private InMemoryRepository repo;
@@ -632,6 +644,205 @@ public void testEstimateUnreachableGarbagePackSize() throws Exception {
}
}
+ @Test
+ public void testSinglePackForAllRefs() throws Exception {
+ RevCommit commit0 = commit().message("0").create();
+ git.update("head", commit0);
+ RevCommit commit1 = commit().message("1").parent(commit0).create();
+ git.update("refs/notes/note1", commit1);
+
+ DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+ gc.setGarbageTtl(0, TimeUnit.MILLISECONDS);
+ gc.getPackConfig().setSinglePack(true);
+ run(gc);
+ assertEquals(1, odb.getPacks().length);
+
+ gc = new DfsGarbageCollector(repo);
+ gc.setGarbageTtl(0, TimeUnit.MILLISECONDS);
+ gc.getPackConfig().setSinglePack(false);
+ run(gc);
+ assertEquals(2, odb.getPacks().length);
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void producesNewReftable() throws Exception {
+ String master = "refs/heads/master";
+ RevCommit commit0 = commit().message("0").create();
+ RevCommit commit1 = commit().message("1").parent(commit0).create();
+
+ BatchRefUpdate bru = git.getRepository().getRefDatabase()
+ .newBatchUpdate();
+ bru.addCommand(new ReceiveCommand(ObjectId.zeroId(), commit1, master));
+ for (int i = 1; i <= 5100; i++) {
+ bru.addCommand(new ReceiveCommand(ObjectId.zeroId(), commit0,
+ String.format("refs/pulls/%04d", i)));
+ }
+ try (RevWalk rw = new RevWalk(git.getRepository())) {
+ bru.execute(rw, NullProgressMonitor.INSTANCE);
+ }
+
+ DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+ gc.setReftableConfig(new ReftableConfig());
+ run(gc);
+
+ // Single GC pack present with all objects.
+ assertEquals(1, odb.getPacks().length);
+ DfsPackFile pack = odb.getPacks()[0];
+ DfsPackDescription desc = pack.getPackDescription();
+ assertEquals(GC, desc.getPackSource());
+ assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
+ assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
+
+ // Sibling REFTABLE is also present.
+ assertTrue(desc.hasFileExt(REFTABLE));
+ ReftableWriter.Stats stats = desc.getReftableStats();
+ assertNotNull(stats);
+ assertTrue(stats.totalBytes() > 0);
+ assertEquals(5101, stats.refCount());
+ assertEquals(1, stats.minUpdateIndex());
+ assertEquals(1, stats.maxUpdateIndex());
+
+ DfsReftable table = new DfsReftable(DfsBlockCache.getInstance(), desc);
+ try (DfsReader ctx = odb.newReader();
+ ReftableReader rr = table.open(ctx);
+ RefCursor rc = rr.seekRef("refs/pulls/5100")) {
+ assertTrue(rc.next());
+ assertEquals(commit0, rc.getRef().getObjectId());
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void leavesNonGcReftablesIfNotConfigured() throws Exception {
+ String master = "refs/heads/master";
+ RevCommit commit0 = commit().message("0").create();
+ RevCommit commit1 = commit().message("1").parent(commit0).create();
+ git.update(master, commit1);
+
+ DfsPackDescription t1 = odb.newPack(INSERT);
+ try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) {
+ out.write("ignored".getBytes(StandardCharsets.UTF_8));
+ t1.addFileExt(REFTABLE);
+ }
+ odb.commitPack(Collections.singleton(t1), null);
+
+ DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+ gc.setReftableConfig(null);
+ run(gc);
+
+ // Single GC pack present with all objects.
+ assertEquals(1, odb.getPacks().length);
+ DfsPackFile pack = odb.getPacks()[0];
+ DfsPackDescription desc = pack.getPackDescription();
+ assertEquals(GC, desc.getPackSource());
+ assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
+ assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
+
+ // Only INSERT REFTABLE above is present.
+ DfsReftable[] tables = odb.getReftables();
+ assertEquals(1, tables.length);
+ assertEquals(t1, tables[0].getPackDescription());
+ }
+
+ @Test
+ public void prunesNonGcReftables() throws Exception {
+ String master = "refs/heads/master";
+ RevCommit commit0 = commit().message("0").create();
+ RevCommit commit1 = commit().message("1").parent(commit0).create();
+ git.update(master, commit1);
+
+ DfsPackDescription t1 = odb.newPack(INSERT);
+ try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) {
+ out.write("ignored".getBytes(StandardCharsets.UTF_8));
+ t1.addFileExt(REFTABLE);
+ }
+ odb.commitPack(Collections.singleton(t1), null);
+ odb.clearCache();
+
+ DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+ gc.setReftableConfig(new ReftableConfig());
+ run(gc);
+
+ // Single GC pack present with all objects.
+ assertEquals(1, odb.getPacks().length);
+ DfsPackFile pack = odb.getPacks()[0];
+ DfsPackDescription desc = pack.getPackDescription();
+ assertEquals(GC, desc.getPackSource());
+ assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
+ assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
+
+ // Only sibling GC REFTABLE is present.
+ DfsReftable[] tables = odb.getReftables();
+ assertEquals(1, tables.length);
+ assertEquals(desc, tables[0].getPackDescription());
+ assertTrue(desc.hasFileExt(REFTABLE));
+ }
+
+ @Test
+ public void compactsReftables() throws Exception {
+ String master = "refs/heads/master";
+ RevCommit commit0 = commit().message("0").create();
+ RevCommit commit1 = commit().message("1").parent(commit0).create();
+ git.update(master, commit1);
+
+ DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+ gc.setReftableConfig(new ReftableConfig());
+ run(gc);
+
+ DfsPackDescription t1 = odb.newPack(INSERT);
+ Ref next = new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE,
+ "refs/heads/next", commit0.copy());
+ try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) {
+ ReftableWriter w = new ReftableWriter();
+ w.setMinUpdateIndex(42);
+ w.setMaxUpdateIndex(42);
+ w.begin(out);
+ w.sortAndWriteRefs(Collections.singleton(next));
+ w.finish();
+ t1.addFileExt(REFTABLE);
+ t1.setReftableStats(w.getStats());
+ }
+ odb.commitPack(Collections.singleton(t1), null);
+
+ gc = new DfsGarbageCollector(repo);
+ gc.setReftableConfig(new ReftableConfig());
+ run(gc);
+
+ // Single GC pack present with all objects.
+ assertEquals(1, odb.getPacks().length);
+ DfsPackFile pack = odb.getPacks()[0];
+ DfsPackDescription desc = pack.getPackDescription();
+ assertEquals(GC, desc.getPackSource());
+ assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
+ assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
+
+ // Only sibling GC REFTABLE is present.
+ DfsReftable[] tables = odb.getReftables();
+ assertEquals(1, tables.length);
+ assertEquals(desc, tables[0].getPackDescription());
+ assertTrue(desc.hasFileExt(REFTABLE));
+
+ // GC reftable contains the compaction.
+ DfsReftable table = new DfsReftable(DfsBlockCache.getInstance(), desc);
+ try (DfsReader ctx = odb.newReader();
+ ReftableReader rr = table.open(ctx);
+ RefCursor rc = rr.allRefs()) {
+ assertEquals(1, rr.minUpdateIndex());
+ assertEquals(42, rr.maxUpdateIndex());
+
+ assertTrue(rc.next());
+ assertEquals(master, rc.getRef().getName());
+ assertEquals(commit1, rc.getRef().getObjectId());
+
+ assertTrue(rc.next());
+ assertEquals(next.getName(), rc.getRef().getName());
+ assertEquals(commit0, rc.getRef().getObjectId());
+
+ assertFalse(rc.next());
+ }
+ }
+
private TestRepository<InMemoryRepository>.CommitBuilder commit() {
return git.commit();
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
new file mode 100644
index 0000000..3c4b8cf
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
@@ -0,0 +1,1054 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.file;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.LOCK_FAILURE;
+import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.OK;
+import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.REJECTED_MISSING_OBJECT;
+import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.REJECTED_NONFASTFORWARD;
+import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.TRANSACTION_ABORTED;
+import static org.eclipse.jgit.lib.ObjectId.zeroId;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.CREATE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Predicate;
+
+import org.eclipse.jgit.events.ListenerHandle;
+import org.eclipse.jgit.events.RefsChangedListener;
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.junit.StrictWorkMonitor;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.CheckoutEntry;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.lib.ReflogReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@SuppressWarnings("boxing")
+@RunWith(Parameterized.class)
+public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
+ @Parameter
+ public boolean atomic;
+
+ @Parameters(name = "atomic={0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{ {Boolean.FALSE}, {Boolean.TRUE} });
+ }
+
+ private Repository diskRepo;
+ private TestRepository<Repository> repo;
+ private RefDirectory refdir;
+ private RevCommit A;
+ private RevCommit B;
+
+ /**
+ * When asserting the number of RefsChangedEvents you must account for one
+ * additional event due to the initial ref setup via a number of calls to
+ * {@link #writeLooseRef(String, AnyObjectId)} (will be fired in execute()
+ * when it is detected that the on-disk loose refs have changed), or for one
+ * additional event per {@link #writeRef(String, AnyObjectId)}.
+ */
+ private int refsChangedEvents;
+
+ private ListenerHandle handle;
+
+ private RefsChangedListener refsChangedListener = event -> {
+ refsChangedEvents++;
+ };
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ diskRepo = createBareRepository();
+ setLogAllRefUpdates(true);
+
+ refdir = (RefDirectory) diskRepo.getRefDatabase();
+ refdir.setRetrySleepMs(Arrays.asList(0, 0));
+
+ repo = new TestRepository<>(diskRepo);
+ A = repo.commit().create();
+ B = repo.commit(repo.getRevWalk().parseCommit(A));
+ refsChangedEvents = 0;
+ handle = diskRepo.getListenerList()
+ .addRefsChangedListener(refsChangedListener);
+ }
+
+ @After
+ public void removeListener() {
+ handle.remove();
+ refsChangedEvents = 0;
+ }
+
+ @Test
+ public void simpleNoForce() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/masters", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(B, A, "refs/heads/masters", UPDATE_NONFASTFORWARD));
+ execute(newBatchUpdate(cmds));
+
+ if (atomic) {
+ assertResults(cmds, TRANSACTION_ABORTED, REJECTED_NONFASTFORWARD);
+ assertRefs(
+ "refs/heads/master", A,
+ "refs/heads/masters", B);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ assertResults(cmds, OK, REJECTED_NONFASTFORWARD);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/masters", B);
+ assertEquals(2, refsChangedEvents);
+ }
+ }
+
+ @Test
+ public void simpleForce() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/masters", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(B, A, "refs/heads/masters", UPDATE_NONFASTFORWARD));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/masters", A);
+ assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ }
+
+ @Test
+ public void nonFastForwardDoesNotDoExpensiveMergeCheck() throws IOException {
+ writeLooseRef("refs/heads/master", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(B, A, "refs/heads/master", UPDATE_NONFASTFORWARD));
+ try (RevWalk rw = new RevWalk(diskRepo) {
+ @Override
+ public boolean isMergedInto(RevCommit base, RevCommit tip) {
+ throw new AssertionError("isMergedInto() should not be called");
+ }
+ }) {
+ newBatchUpdate(cmds)
+ .setAllowNonFastForwards(true)
+ .execute(rw, new StrictWorkMonitor());
+ }
+
+ assertResults(cmds, OK);
+ assertRefs("refs/heads/master", A);
+ assertEquals(2, refsChangedEvents);
+ }
+
+ @Test
+ public void fileDirectoryConflict() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/masters", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE),
+ new ReceiveCommand(zeroId(), A, "refs/heads", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
+
+ if (atomic) {
+ // Atomic update sees that master and master/x are conflicting, then marks
+ // the first one in the list as LOCK_FAILURE and aborts the rest.
+ assertResults(cmds,
+ LOCK_FAILURE, TRANSACTION_ABORTED, TRANSACTION_ABORTED);
+ assertRefs(
+ "refs/heads/master", A,
+ "refs/heads/masters", B);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ // Non-atomic updates are applied in order: master succeeds, then master/x
+ // fails due to conflict.
+ assertResults(cmds, OK, LOCK_FAILURE, LOCK_FAILURE);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/masters", B);
+ assertEquals(2, refsChangedEvents);
+ }
+ }
+
+ @Test
+ public void conflictThanksToDelete() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/masters", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), A, "refs/heads/masters/x", CREATE),
+ new ReceiveCommand(B, zeroId(), "refs/heads/masters", DELETE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertResults(cmds, OK, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/masters/x", A);
+ if (atomic) {
+ assertEquals(2, refsChangedEvents);
+ } else {
+ // The non-atomic case actually produces 5 events, but that's an
+ // implementation detail. We expect at least 4 events, one for the
+ // initial read due to writeLooseRef(), and then one for each
+ // successful ref update.
+ assertTrue(refsChangedEvents >= 4);
+ }
+ }
+
+ @Test
+ public void updateToMissingObject() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+
+ ObjectId bad =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, bad, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
+
+ if (atomic) {
+ assertResults(cmds, REJECTED_MISSING_OBJECT, TRANSACTION_ABORTED);
+ assertRefs("refs/heads/master", A);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ assertResults(cmds, REJECTED_MISSING_OBJECT, OK);
+ assertRefs(
+ "refs/heads/master", A,
+ "refs/heads/foo2", B);
+ assertEquals(2, refsChangedEvents);
+ }
+ }
+
+ @Test
+ public void addMissingObject() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+
+ ObjectId bad =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), bad, "refs/heads/foo2", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false);
+
+ if (atomic) {
+ assertResults(cmds, TRANSACTION_ABORTED, REJECTED_MISSING_OBJECT);
+ assertRefs("refs/heads/master", A);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ assertResults(cmds, OK, REJECTED_MISSING_OBJECT);
+ assertRefs("refs/heads/master", B);
+ assertEquals(2, refsChangedEvents);
+ }
+ }
+
+ @Test
+ public void oneNonExistentRef() throws IOException {
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/foo1", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ if (atomic) {
+ assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
+ assertRefs();
+ assertEquals(0, refsChangedEvents);
+ } else {
+ assertResults(cmds, LOCK_FAILURE, OK);
+ assertRefs("refs/heads/foo2", B);
+ assertEquals(1, refsChangedEvents);
+ }
+ }
+
+ @Test
+ public void oneRefWrongOldValue() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(B, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ if (atomic) {
+ assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
+ assertRefs("refs/heads/master", A);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ assertResults(cmds, LOCK_FAILURE, OK);
+ assertRefs(
+ "refs/heads/master", A,
+ "refs/heads/foo2", B);
+ assertEquals(2, refsChangedEvents);
+ }
+ }
+
+ @Test
+ public void nonExistentRef() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(A, zeroId(), "refs/heads/foo2", DELETE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ if (atomic) {
+ assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
+ assertRefs("refs/heads/master", A);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ assertResults(cmds, OK, LOCK_FAILURE);
+ assertRefs("refs/heads/master", B);
+ assertEquals(2, refsChangedEvents);
+ }
+ }
+
+ @Test
+ public void noRefLog() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ Map<String, ReflogEntry> oldLogs =
+ getLastReflogs("refs/heads/master", "refs/heads/branch");
+ assertEquals(Collections.singleton("refs/heads/master"), oldLogs.keySet());
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch", B);
+ assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertReflogUnchanged(oldLogs, "refs/heads/master");
+ assertReflogUnchanged(oldLogs, "refs/heads/branch");
+ }
+
+ @Test
+ public void reflogDefaultIdent() throws IOException {
+ writeRef("refs/heads/master", A);
+ writeRef("refs/heads/branch2", A);
+
+ Map<String, ReflogEntry> oldLogs = getLastReflogs(
+ "refs/heads/master", "refs/heads/branch1", "refs/heads/branch2");
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch1", CREATE));
+ execute(
+ newBatchUpdate(cmds)
+ .setAllowNonFastForwards(true)
+ .setRefLogMessage("a reflog", false));
+
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch1", B,
+ "refs/heads/branch2", A);
+ assertEquals(atomic ? 3 : 4, refsChangedEvents);
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/branch1"));
+ assertReflogUnchanged(oldLogs, "refs/heads/branch2");
+ }
+
+ @Test
+ public void reflogAppendStatusNoMessage() throws IOException {
+ writeRef("refs/heads/master", A);
+ writeRef("refs/heads/branch1", B);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(B, A, "refs/heads/branch1", UPDATE_NONFASTFORWARD),
+ new ReceiveCommand(zeroId(), A, "refs/heads/branch2", CREATE));
+ execute(
+ newBatchUpdate(cmds)
+ .setAllowNonFastForwards(true)
+ .setRefLogMessage(null, true));
+
+ assertResults(cmds, OK, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch1", A,
+ "refs/heads/branch2", A);
+ assertEquals(atomic ? 3 : 5, refsChangedEvents);
+ assertReflogEquals(
+ // Always forced; setAllowNonFastForwards(true) bypasses the check.
+ reflog(A, B, new PersonIdent(diskRepo), "forced-update"),
+ getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(B, A, new PersonIdent(diskRepo), "forced-update"),
+ getLastReflog("refs/heads/branch1"));
+ assertReflogEquals(
+ reflog(zeroId(), A, new PersonIdent(diskRepo), "created"),
+ getLastReflog("refs/heads/branch2"));
+ }
+
+ @Test
+ public void reflogAppendStatusFastForward() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage(null, true));
+
+ assertResults(cmds, OK);
+ assertRefs("refs/heads/master", B);
+ assertEquals(2, refsChangedEvents);
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "fast-forward"),
+ getLastReflog("refs/heads/master"));
+ }
+
+ @Test
+ public void reflogAppendStatusWithMessage() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), A, "refs/heads/branch", CREATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true));
+
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch", A);
+ assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "a reflog: fast-forward"),
+ getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(zeroId(), A, new PersonIdent(diskRepo), "a reflog: created"),
+ getLastReflog("refs/heads/branch"));
+ }
+
+ @Test
+ public void reflogCustomIdent() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ PersonIdent ident = new PersonIdent("A Reflog User", "reflog@example.com");
+ execute(
+ newBatchUpdate(cmds)
+ .setRefLogMessage("a reflog", false)
+ .setRefLogIdent(ident));
+
+ assertResults(cmds, OK, OK);
+ assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch", B);
+ assertReflogEquals(
+ reflog(A, B, ident, "a reflog"),
+ getLastReflog("refs/heads/master"),
+ true);
+ assertReflogEquals(
+ reflog(zeroId(), B, ident, "a reflog"),
+ getLastReflog("refs/heads/branch"),
+ true);
+ }
+
+ @Test
+ public void reflogDelete() throws IOException {
+ writeRef("refs/heads/master", A);
+ writeRef("refs/heads/branch", A);
+ assertEquals(
+ 2, getLastReflogs("refs/heads/master", "refs/heads/branch").size());
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, zeroId(), "refs/heads/master", DELETE),
+ new ReceiveCommand(A, B, "refs/heads/branch", UPDATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
+
+ assertResults(cmds, OK, OK);
+ assertRefs("refs/heads/branch", B);
+ assertEquals(atomic ? 3 : 4, refsChangedEvents);
+ assertNull(getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/branch"));
+ }
+
+ @Test
+ public void reflogFileDirectoryConflict() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, zeroId(), "refs/heads/master", DELETE),
+ new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
+
+ assertResults(cmds, OK, OK);
+ assertRefs("refs/heads/master/x", A);
+ assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertNull(getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(zeroId(), A, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/master/x"));
+ }
+
+ @Test
+ public void reflogOnLockFailure() throws IOException {
+ writeRef("refs/heads/master", A);
+
+ Map<String, ReflogEntry> oldLogs =
+ getLastReflogs("refs/heads/master", "refs/heads/branch");
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(A, B, "refs/heads/branch", UPDATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
+
+ if (atomic) {
+ assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
+ assertEquals(1, refsChangedEvents);
+ assertReflogUnchanged(oldLogs, "refs/heads/master");
+ assertReflogUnchanged(oldLogs, "refs/heads/branch");
+ } else {
+ assertResults(cmds, OK, LOCK_FAILURE);
+ assertEquals(2, refsChangedEvents);
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/master"));
+ assertReflogUnchanged(oldLogs, "refs/heads/branch");
+ }
+ }
+
+ @Test
+ public void overrideRefLogMessage() throws Exception {
+ writeRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ cmds.get(0).setRefLogMessage("custom log", false);
+ PersonIdent ident = new PersonIdent(diskRepo);
+ execute(
+ newBatchUpdate(cmds)
+ .setRefLogIdent(ident)
+ .setRefLogMessage("a reflog", true));
+
+ assertResults(cmds, OK, OK);
+ assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertReflogEquals(
+ reflog(A, B, ident, "custom log"),
+ getLastReflog("refs/heads/master"),
+ true);
+ assertReflogEquals(
+ reflog(zeroId(), B, ident, "a reflog: created"),
+ getLastReflog("refs/heads/branch"),
+ true);
+ }
+
+ @Test
+ public void overrideDisableRefLog() throws Exception {
+ writeRef("refs/heads/master", A);
+
+ Map<String, ReflogEntry> oldLogs =
+ getLastReflogs("refs/heads/master", "refs/heads/branch");
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ cmds.get(0).disableRefLog();
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true));
+
+ assertResults(cmds, OK, OK);
+ assertEquals(atomic ? 2 : 3, refsChangedEvents);
+ assertReflogUnchanged(oldLogs, "refs/heads/master");
+ assertReflogEquals(
+ reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog: created"),
+ getLastReflog("refs/heads/branch"));
+ }
+
+ @Test
+ public void refLogNotWrittenWithoutConfigOption() throws Exception {
+ setLogAllRefUpdates(false);
+ writeRef("refs/heads/master", A);
+
+ Map<String, ReflogEntry> oldLogs =
+ getLastReflogs("refs/heads/master", "refs/heads/branch");
+ assertTrue(oldLogs.isEmpty());
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
+
+ assertResults(cmds, OK, OK);
+ assertReflogUnchanged(oldLogs, "refs/heads/master");
+ assertReflogUnchanged(oldLogs, "refs/heads/branch");
+ }
+
+ @Test
+ public void forceRefLogInUpdate() throws Exception {
+ setLogAllRefUpdates(false);
+ writeRef("refs/heads/master", A);
+ assertTrue(
+ getLastReflogs("refs/heads/master", "refs/heads/branch").isEmpty());
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ execute(
+ newBatchUpdate(cmds)
+ .setRefLogMessage("a reflog", false)
+ .setForceRefLog(true));
+
+ assertResults(cmds, OK, OK);
+ assertReflogEquals(
+ reflog(A, B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/master"));
+ assertReflogEquals(
+ reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/branch"));
+ }
+
+ @Test
+ public void forceRefLogInCommand() throws Exception {
+ setLogAllRefUpdates(false);
+ writeRef("refs/heads/master", A);
+
+ Map<String, ReflogEntry> oldLogs =
+ getLastReflogs("refs/heads/master", "refs/heads/branch");
+ assertTrue(oldLogs.isEmpty());
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+ cmds.get(1).setForceRefLog(true);
+ execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", false));
+
+ assertResults(cmds, OK, OK);
+ assertReflogUnchanged(oldLogs, "refs/heads/master");
+ assertReflogEquals(
+ reflog(zeroId(), B, new PersonIdent(diskRepo), "a reflog"),
+ getLastReflog("refs/heads/branch"));
+ }
+
+ @Test
+ public void packedRefsLockFailure() throws Exception {
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+
+ LockFile myLock = refdir.lockPackedRefs();
+ try {
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertFalse(getLockFile("refs/heads/master").exists());
+ assertFalse(getLockFile("refs/heads/branch").exists());
+
+ if (atomic) {
+ assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
+ assertRefs("refs/heads/master", A);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ // Only operates on loose refs, doesn't care that packed-refs is locked.
+ assertResults(cmds, OK, OK);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch", B);
+ assertEquals(3, refsChangedEvents);
+ }
+ } finally {
+ myLock.unlock();
+ }
+ }
+
+ @Test
+ public void oneRefLockFailure() throws Exception {
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE),
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
+
+ LockFile myLock = new LockFile(refdir.fileFor("refs/heads/master"));
+ assertTrue(myLock.lock());
+ try {
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertFalse(LockFile.getLockFile(refdir.packedRefsFile).exists());
+ assertFalse(getLockFile("refs/heads/branch").exists());
+
+ if (atomic) {
+ assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
+ assertRefs("refs/heads/master", A);
+ assertEquals(1, refsChangedEvents);
+ } else {
+ assertResults(cmds, OK, LOCK_FAILURE);
+ assertRefs(
+ "refs/heads/branch", B,
+ "refs/heads/master", A);
+ assertEquals(2, refsChangedEvents);
+ }
+ } finally {
+ myLock.unlock();
+ }
+ }
+
+ @Test
+ public void singleRefUpdateDoesNotRequirePackedRefsLock() throws Exception {
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
+
+ LockFile myLock = refdir.lockPackedRefs();
+ try {
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+
+ assertFalse(getLockFile("refs/heads/master").exists());
+ assertResults(cmds, OK);
+ assertEquals(2, refsChangedEvents);
+ assertRefs("refs/heads/master", B);
+ } finally {
+ myLock.unlock();
+ }
+ }
+
+ @Test
+ public void atomicUpdateRespectsInProcessLock() throws Exception {
+ assumeTrue(atomic);
+
+ writeLooseRef("refs/heads/master", A);
+
+ List<ReceiveCommand> cmds = Arrays.asList(
+ new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
+ new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
+
+ Thread t = new Thread(() -> {
+ try {
+ execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ ReentrantLock l = refdir.inProcessPackedRefsLock;
+ l.lock();
+ try {
+ t.start();
+ long timeoutSecs = 10;
+ long startNanos = System.nanoTime();
+
+ // Hold onto the lock until we observe the worker thread has attempted to
+ // acquire it.
+ while (l.getQueueLength() == 0) {
+ long elapsedNanos = System.nanoTime() - startNanos;
+ assertTrue(
+ "timed out waiting for work thread to attempt to acquire lock",
+ NANOSECONDS.toSeconds(elapsedNanos) < timeoutSecs);
+ Thread.sleep(3);
+ }
+
+ // Once we unlock, the worker thread should finish the update promptly.
+ l.unlock();
+ t.join(SECONDS.toMillis(timeoutSecs));
+ assertFalse(t.isAlive());
+ } finally {
+ if (l.isHeldByCurrentThread()) {
+ l.unlock();
+ }
+ }
+
+ assertResults(cmds, OK, OK);
+ assertEquals(2, refsChangedEvents);
+ assertRefs(
+ "refs/heads/master", B,
+ "refs/heads/branch", B);
+ }
+
+ private void setLogAllRefUpdates(boolean enable) throws Exception {
+ StoredConfig cfg = diskRepo.getConfig();
+ cfg.load();
+ cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, enable);
+ cfg.save();
+ }
+
+ private void writeLooseRef(String name, AnyObjectId id) throws IOException {
+ write(new File(diskRepo.getDirectory(), name), id.name() + "\n");
+ }
+
+ private void writeRef(String name, AnyObjectId id) throws IOException {
+ RefUpdate u = diskRepo.updateRef(name);
+ u.setRefLogMessage(getClass().getSimpleName(), false);
+ u.setForceUpdate(true);
+ u.setNewObjectId(id);
+ RefUpdate.Result r = u.update();
+ switch (r) {
+ case NEW:
+ case FORCED:
+ return;
+ default:
+ throw new IOException("Got " + r + " while updating " + name);
+ }
+ }
+
+ private BatchRefUpdate newBatchUpdate(List<ReceiveCommand> cmds) {
+ BatchRefUpdate u = refdir.newBatchUpdate();
+ if (atomic) {
+ assertTrue(u.isAtomic());
+ } else {
+ u.setAtomic(false);
+ }
+ u.addCommand(cmds);
+ return u;
+ }
+
+ private void execute(BatchRefUpdate u) throws IOException {
+ execute(u, false);
+ }
+
+ private void execute(BatchRefUpdate u, boolean strictWork) throws IOException {
+ try (RevWalk rw = new RevWalk(diskRepo)) {
+ u.execute(rw,
+ strictWork ? new StrictWorkMonitor() : NullProgressMonitor.INSTANCE);
+ }
+ }
+
+ private void assertRefs(Object... args) throws IOException {
+ if (args.length % 2 != 0) {
+ throw new IllegalArgumentException(
+ "expected even number of args: " + Arrays.toString(args));
+ }
+
+ Map<String, AnyObjectId> expected = new LinkedHashMap<>();
+ for (int i = 0; i < args.length; i += 2) {
+ expected.put((String) args[i], (AnyObjectId) args[i + 1]);
+ }
+
+ Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
+ Ref actualHead = refs.remove(Constants.HEAD);
+ if (actualHead != null) {
+ String actualLeafName = actualHead.getLeaf().getName();
+ assertEquals(
+ "expected HEAD to point to refs/heads/master, got: " + actualLeafName,
+ "refs/heads/master", actualLeafName);
+ AnyObjectId expectedMaster = expected.get("refs/heads/master");
+ assertNotNull("expected master ref since HEAD exists", expectedMaster);
+ assertEquals(expectedMaster, actualHead.getObjectId());
+ }
+
+ Map<String, AnyObjectId> actual = new LinkedHashMap<>();
+ refs.forEach((n, r) -> actual.put(n, r.getObjectId()));
+
+ assertEquals(expected.keySet(), actual.keySet());
+ actual.forEach((n, a) -> assertEquals(n, expected.get(n), a));
+ }
+
+ enum Result {
+ OK(ReceiveCommand.Result.OK),
+ LOCK_FAILURE(ReceiveCommand.Result.LOCK_FAILURE),
+ REJECTED_NONFASTFORWARD(ReceiveCommand.Result.REJECTED_NONFASTFORWARD),
+ REJECTED_MISSING_OBJECT(ReceiveCommand.Result.REJECTED_MISSING_OBJECT),
+ TRANSACTION_ABORTED(ReceiveCommand::isTransactionAborted);
+
+ final Predicate<? super ReceiveCommand> p;
+
+ private Result(Predicate<? super ReceiveCommand> p) {
+ this.p = p;
+ }
+
+ private Result(ReceiveCommand.Result result) {
+ this(c -> c.getResult() == result);
+ }
+ }
+
+ private void assertResults(
+ List<ReceiveCommand> cmds, Result... expected) {
+ if (expected.length != cmds.size()) {
+ throw new IllegalArgumentException(
+ "expected " + cmds.size() + " result args");
+ }
+ for (int i = 0; i < cmds.size(); i++) {
+ ReceiveCommand c = cmds.get(i);
+ Result r = expected[i];
+ assertTrue(
+ String.format(
+ "result of command (%d) should be %s: %s %s%s",
+ Integer.valueOf(i), r, c,
+ c.getResult(),
+ c.getMessage() != null ? " (" + c.getMessage() + ")" : ""),
+ r.p.test(c));
+ }
+ }
+
+ private Map<String, ReflogEntry> getLastReflogs(String... names)
+ throws IOException {
+ Map<String, ReflogEntry> result = new LinkedHashMap<>();
+ for (String name : names) {
+ ReflogEntry e = getLastReflog(name);
+ if (e != null) {
+ result.put(name, e);
+ }
+ }
+ return result;
+ }
+
+ private ReflogEntry getLastReflog(String name) throws IOException {
+ ReflogReader r = diskRepo.getReflogReader(name);
+ if (r == null) {
+ return null;
+ }
+ return r.getLastEntry();
+ }
+
+ private File getLockFile(String refName) {
+ return LockFile.getLockFile(refdir.fileFor(refName));
+ }
+
+ private void assertReflogUnchanged(
+ Map<String, ReflogEntry> old, String name) throws IOException {
+ assertReflogEquals(old.get(name), getLastReflog(name), true);
+ }
+
+ private static void assertReflogEquals(
+ ReflogEntry expected, ReflogEntry actual) {
+ assertReflogEquals(expected, actual, false);
+ }
+
+ private static void assertReflogEquals(
+ ReflogEntry expected, ReflogEntry actual, boolean strictTime) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertNotNull(actual);
+ assertEquals(expected.getOldId(), actual.getOldId());
+ assertEquals(expected.getNewId(), actual.getNewId());
+ if (strictTime) {
+ assertEquals(expected.getWho(), actual.getWho());
+ } else {
+ assertEquals(expected.getWho().getName(), actual.getWho().getName());
+ assertEquals(
+ expected.getWho().getEmailAddress(),
+ actual.getWho().getEmailAddress());
+ }
+ assertEquals(expected.getComment(), actual.getComment());
+ }
+
+ private static ReflogEntry reflog(ObjectId oldId, ObjectId newId,
+ PersonIdent who, String comment) {
+ return new ReflogEntry() {
+ @Override
+ public ObjectId getOldId() {
+ return oldId;
+ }
+
+ @Override
+ public ObjectId getNewId() {
+ return newId;
+ }
+
+ @Override
+ public PersonIdent getWho() {
+ return who;
+ }
+
+ @Override
+ public String getComment() {
+ return comment;
+ }
+
+ @Override
+ public CheckoutEntry parseCheckout() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
index 643bb49..c60c357 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
@@ -47,27 +47,35 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import org.eclipse.jgit.errors.CancelledException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.pack.PackWriter;
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.EmptyProgressMonitor;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Sets;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Test;
public class GcConcurrentTest extends GcTestCase {
@@ -221,4 +229,48 @@ public void repackAndCheckBitmapUsage() throws Exception {
assertEquals(getSinglePack(repository).getPackName(), newPackName);
assertNotNull(getSinglePack(repository).getBitmapIndex());
}
+
+ @Test
+ public void testInterruptGc() throws Exception {
+ FileBasedConfig c = repo.getConfig();
+ c.setInt(ConfigConstants.CONFIG_GC_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOPACKLIMIT, 1);
+ c.save();
+ SampleDataRepositoryTestCase.copyCGitTestPacks(repo);
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Future<Collection<PackFile>> result = executor
+ .submit(new Callable<Collection<PackFile>>() {
+
+ @Override
+ public Collection<PackFile> call() throws Exception {
+ long start = System.currentTimeMillis();
+ System.out.println("starting gc");
+ latch.countDown();
+ Collection<PackFile> r = gc.gc();
+ System.out.println("gc took "
+ + (System.currentTimeMillis() - start) + " ms");
+ return r;
+ }
+ });
+ try {
+ latch.await();
+ Thread.sleep(5);
+ executor.shutdownNow();
+ result.get();
+ fail("thread wasn't interrupted");
+ } catch (ExecutionException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof CancelledException) {
+ assertEquals(JGitText.get().operationCanceled,
+ cause.getMessage());
+ } else if (cause instanceof IOException) {
+ Throwable cause2 = cause.getCause();
+ assertTrue(cause2 instanceof InterruptedException
+ || cause2 instanceof ExecutionException);
+ } else {
+ fail("unexpected exception " + e);
+ }
+ }
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
index 11a2a22..c43bdbd 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
@@ -43,12 +43,13 @@
package org.eclipse.jgit.internal.storage.file;
-import static java.lang.Integer.valueOf;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
import java.io.File;
import java.io.IOException;
@@ -74,6 +75,7 @@
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.junit.Test;
+@SuppressWarnings("boxing")
public class GcPackRefsTest extends GcTestCase {
@Test
public void looseRefPacked() throws Exception {
@@ -100,27 +102,23 @@ public void concurrentOnlyOneWritesPackedRefs() throws Exception {
RevBlob a = tr.blob("a");
tr.lightweightTag("t", a);
- final CyclicBarrier syncPoint = new CyclicBarrier(2);
+ CyclicBarrier syncPoint = new CyclicBarrier(2);
- Callable<Integer> packRefs = new Callable<Integer>() {
-
- /** @return 0 for success, 1 in case of error when writing pack */
- @Override
- public Integer call() throws Exception {
- syncPoint.await();
- try {
- gc.packRefs();
- return valueOf(0);
- } catch (IOException e) {
- return valueOf(1);
- }
+ // Returns 0 for success, 1 in case of error when writing pack.
+ Callable<Integer> packRefs = () -> {
+ syncPoint.await();
+ try {
+ gc.packRefs();
+ return 0;
+ } catch (IOException e) {
+ return 1;
}
};
ExecutorService pool = Executors.newFixedThreadPool(2);
try {
Future<Integer> p1 = pool.submit(packRefs);
Future<Integer> p2 = pool.submit(packRefs);
- assertEquals(1, p1.get().intValue() + p2.get().intValue());
+ assertThat(p1.get() + p2.get(), lessThanOrEqualTo(1));
} finally {
pool.shutdown();
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTemporaryFilesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTemporaryFilesTest.java
new file mode 100644
index 0000000..59d544e
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTemporaryFilesTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 Ericsson
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.file;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.nio.file.Paths;
+import java.time.Instant;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class GcTemporaryFilesTest extends GcTestCase {
+ private static final String TEMP_IDX = "gc_1234567890.idx_tmp";
+
+ private static final String TEMP_PACK = "gc_1234567890.pack_tmp";
+
+ private File packDir;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ packDir = Paths.get(repo.getObjectsDirectory().getAbsolutePath(),
+ "pack").toFile(); //$NON-NLS-1$
+ }
+
+ @Test
+ public void oldTempPacksAndIdxAreDeleted() throws Exception {
+ File tempIndex = new File(packDir, TEMP_IDX);
+ File tempPack = new File(packDir, TEMP_PACK);
+ if (!packDir.exists() || !packDir.isDirectory()) {
+ assertTrue(packDir.mkdirs());
+ }
+ assertTrue(tempPack.createNewFile());
+ assertTrue(tempIndex.createNewFile());
+ assertTrue(tempIndex.exists());
+ assertTrue(tempPack.exists());
+ long _24HoursBefore = Instant.now().toEpochMilli()
+ - 24 * 60 * 62 * 1000;
+ tempIndex.setLastModified(_24HoursBefore);
+ tempPack.setLastModified(_24HoursBefore);
+ gc.gc();
+ assertFalse(tempIndex.exists());
+ assertFalse(tempPack.exists());
+ }
+
+ @Test
+ public void recentTempPacksAndIdxAreNotDeleted() throws Exception {
+ File tempIndex = new File(packDir, TEMP_IDX);
+ File tempPack = new File(packDir, TEMP_PACK);
+ if (!packDir.exists() || !packDir.isDirectory()) {
+ assertTrue(packDir.mkdirs());
+ }
+ assertTrue(tempPack.createNewFile());
+ assertTrue(tempIndex.createNewFile());
+ assertTrue(tempIndex.exists());
+ assertTrue(tempPack.exists());
+ gc.gc();
+ assertTrue(tempIndex.exists());
+ assertTrue(tempPack.exists());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java
new file mode 100644
index 0000000..8596f74
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.file;
+
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
+
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.ObjectStream;
+import org.eclipse.jgit.storage.file.WindowCacheConfig;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.util.IO;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+@SuppressWarnings("boxing")
+public class PackInserterTest extends RepositoryTestCase {
+ private WindowCacheConfig origWindowCacheConfig;
+
+ @Before
+ public void setWindowCacheConfig() {
+ origWindowCacheConfig = new WindowCacheConfig();
+ origWindowCacheConfig.install();
+ }
+
+ @After
+ public void resetWindowCacheConfig() {
+ origWindowCacheConfig.install();
+ }
+
+ @Before
+ public void emptyAtSetUp() throws Exception {
+ assertEquals(0, listPacks().size());
+ assertNoObjects();
+ }
+
+ @Test
+ public void noFlush() throws Exception {
+ try (PackInserter ins = newInserter()) {
+ ins.insert(OBJ_BLOB, Constants.encode("foo contents"));
+ // No flush.
+ }
+ assertNoObjects();
+ }
+
+ @Test
+ public void flushEmptyPack() throws Exception {
+ try (PackInserter ins = newInserter()) {
+ ins.flush();
+ }
+ assertNoObjects();
+ }
+
+ @Test
+ public void singlePack() throws Exception {
+ ObjectId blobId;
+ byte[] blob = Constants.encode("foo contents");
+ ObjectId treeId;
+ ObjectId commitId;
+ byte[] commit;
+ try (PackInserter ins = newInserter()) {
+ blobId = ins.insert(OBJ_BLOB, blob);
+
+ DirCache dc = DirCache.newInCore();
+ DirCacheBuilder b = dc.builder();
+ DirCacheEntry dce = new DirCacheEntry("foo");
+ dce.setFileMode(FileMode.REGULAR_FILE);
+ dce.setObjectId(blobId);
+ b.add(dce);
+ b.finish();
+ treeId = dc.writeTree(ins);
+
+ CommitBuilder cb = new CommitBuilder();
+ cb.setTreeId(treeId);
+ cb.setAuthor(author);
+ cb.setCommitter(committer);
+ cb.setMessage("Commit message");
+ commit = cb.toByteArray();
+ commitId = ins.insert(cb);
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ List<PackFile> packs = listPacks();
+ assertEquals(1, packs.size());
+ assertEquals(3, packs.get(0).getObjectCount());
+
+ try (ObjectReader reader = db.newObjectReader()) {
+ assertBlob(reader, blobId, blob);
+
+ CanonicalTreeParser treeParser =
+ new CanonicalTreeParser(null, reader, treeId);
+ assertEquals("foo", treeParser.getEntryPathString());
+ assertEquals(blobId, treeParser.getEntryObjectId());
+
+ ObjectLoader commitLoader = reader.open(commitId);
+ assertEquals(OBJ_COMMIT, commitLoader.getType());
+ assertArrayEquals(commit, commitLoader.getBytes());
+ }
+ }
+
+ @Test
+ public void multiplePacks() throws Exception {
+ ObjectId blobId1;
+ ObjectId blobId2;
+ byte[] blob1 = Constants.encode("blob1");
+ byte[] blob2 = Constants.encode("blob2");
+
+ try (PackInserter ins = newInserter()) {
+ blobId1 = ins.insert(OBJ_BLOB, blob1);
+ ins.flush();
+ blobId2 = ins.insert(OBJ_BLOB, blob2);
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ List<PackFile> packs = listPacks();
+ assertEquals(2, packs.size());
+ assertEquals(1, packs.get(0).getObjectCount());
+ assertEquals(1, packs.get(1).getObjectCount());
+
+ try (ObjectReader reader = db.newObjectReader()) {
+ assertBlob(reader, blobId1, blob1);
+ assertBlob(reader, blobId2, blob2);
+ }
+ }
+
+ @Test
+ public void largeBlob() throws Exception {
+ ObjectId blobId;
+ byte[] blob = newLargeBlob();
+ try (PackInserter ins = newInserter()) {
+ assertThat(blob.length, greaterThan(ins.getBufferSize()));
+ blobId =
+ ins.insert(OBJ_BLOB, blob.length, new ByteArrayInputStream(blob));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ Collection<PackFile> packs = listPacks();
+ assertEquals(1, packs.size());
+ PackFile p = packs.iterator().next();
+ assertEquals(1, p.getObjectCount());
+
+ try (ObjectReader reader = db.newObjectReader()) {
+ assertBlob(reader, blobId, blob);
+ }
+ }
+
+ @Test
+ public void overwriteExistingPack() throws Exception {
+ ObjectId blobId;
+ byte[] blob = Constants.encode("foo contents");
+
+ try (PackInserter ins = newInserter()) {
+ blobId = ins.insert(OBJ_BLOB, blob);
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ List<PackFile> packs = listPacks();
+ assertEquals(1, packs.size());
+ PackFile pack = packs.get(0);
+ assertEquals(1, pack.getObjectCount());
+
+ String inode = getInode(pack.getPackFile());
+
+ try (PackInserter ins = newInserter()) {
+ ins.checkExisting(false);
+ assertEquals(blobId, ins.insert(OBJ_BLOB, blob));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ packs = listPacks();
+ assertEquals(1, packs.size());
+ pack = packs.get(0);
+ assertEquals(1, pack.getObjectCount());
+
+ if (inode != null) {
+ // Old file was overwritten with new file, although objects were
+ // equivalent.
+ assertNotEquals(inode, getInode(pack.getPackFile()));
+ }
+ }
+
+ @Test
+ public void checkExisting() throws Exception {
+ ObjectId blobId;
+ byte[] blob = Constants.encode("foo contents");
+
+ try (PackInserter ins = newInserter()) {
+ blobId = ins.insert(OBJ_BLOB, blob);
+ ins.insert(OBJ_BLOB, Constants.encode("another blob"));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ assertEquals(1, listPacks().size());
+
+ try (PackInserter ins = newInserter()) {
+ assertEquals(blobId, ins.insert(OBJ_BLOB, blob));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ assertEquals(1, listPacks().size());
+
+ try (PackInserter ins = newInserter()) {
+ ins.checkExisting(false);
+ assertEquals(blobId, ins.insert(OBJ_BLOB, blob));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ assertEquals(2, listPacks().size());
+
+ try (ObjectReader reader = db.newObjectReader()) {
+ assertBlob(reader, blobId, blob);
+ }
+ }
+
+ @Test
+ public void insertSmallInputStreamRespectsCheckExisting() throws Exception {
+ ObjectId blobId;
+ byte[] blob = Constants.encode("foo contents");
+ try (PackInserter ins = newInserter()) {
+ assertThat(blob.length, lessThan(ins.getBufferSize()));
+ blobId = ins.insert(OBJ_BLOB, blob);
+ ins.insert(OBJ_BLOB, Constants.encode("another blob"));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ assertEquals(1, listPacks().size());
+
+ try (PackInserter ins = newInserter()) {
+ assertEquals(blobId,
+ ins.insert(OBJ_BLOB, blob.length, new ByteArrayInputStream(blob)));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ assertEquals(1, listPacks().size());
+ }
+
+ @Test
+ public void insertLargeInputStreamBypassesCheckExisting() throws Exception {
+ ObjectId blobId;
+ byte[] blob = newLargeBlob();
+
+ try (PackInserter ins = newInserter()) {
+ assertThat(blob.length, greaterThan(ins.getBufferSize()));
+ blobId = ins.insert(OBJ_BLOB, blob);
+ ins.insert(OBJ_BLOB, Constants.encode("another blob"));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ assertEquals(1, listPacks().size());
+
+ try (PackInserter ins = newInserter()) {
+ assertEquals(blobId,
+ ins.insert(OBJ_BLOB, blob.length, new ByteArrayInputStream(blob)));
+ ins.flush();
+ }
+
+ assertPacksOnly();
+ assertEquals(2, listPacks().size());
+ }
+
+ @Test
+ public void readBackSmallFiles() throws Exception {
+ ObjectId blobId1;
+ ObjectId blobId2;
+ ObjectId blobId3;
+ byte[] blob1 = Constants.encode("blob1");
+ byte[] blob2 = Constants.encode("blob2");
+ byte[] blob3 = Constants.encode("blob3");
+ try (PackInserter ins = newInserter()) {
+ assertThat(blob1.length, lessThan(ins.getBufferSize()));
+ blobId1 = ins.insert(OBJ_BLOB, blob1);
+
+ try (ObjectReader reader = ins.newReader()) {
+ assertBlob(reader, blobId1, blob1);
+ }
+
+ // Read-back should not mess up the file pointer.
+ blobId2 = ins.insert(OBJ_BLOB, blob2);
+ ins.flush();
+
+ blobId3 = ins.insert(OBJ_BLOB, blob3);
+ }
+
+ assertPacksOnly();
+ List<PackFile> packs = listPacks();
+ assertEquals(1, packs.size());
+ assertEquals(2, packs.get(0).getObjectCount());
+
+ try (ObjectReader reader = db.newObjectReader()) {
+ assertBlob(reader, blobId1, blob1);
+ assertBlob(reader, blobId2, blob2);
+
+ try {
+ reader.open(blobId3);
+ fail("Expected MissingObjectException");
+ } catch (MissingObjectException expected) {
+ // Expected.
+ }
+ }
+ }
+
+ @Test
+ public void readBackLargeFile() throws Exception {
+ ObjectId blobId;
+ byte[] blob = newLargeBlob();
+
+ WindowCacheConfig wcc = new WindowCacheConfig();
+ wcc.setStreamFileThreshold(1024);
+ wcc.install();
+ try (ObjectReader reader = db.newObjectReader()) {
+ assertThat(blob.length, greaterThan(reader.getStreamFileThreshold()));
+ }
+
+ try (PackInserter ins = newInserter()) {
+ blobId = ins.insert(OBJ_BLOB, blob);
+
+ try (ObjectReader reader = ins.newReader()) {
+ // Double-check threshold is propagated.
+ assertThat(blob.length, greaterThan(reader.getStreamFileThreshold()));
+ assertBlob(reader, blobId, blob);
+ }
+ }
+
+ assertPacksOnly();
+ // Pack was streamed out to disk and read back from the temp file, but
+ // ultimately rolled back and deleted.
+ assertEquals(0, listPacks().size());
+
+ try (ObjectReader reader = db.newObjectReader()) {
+ try {
+ reader.open(blobId);
+ fail("Expected MissingObjectException");
+ } catch (MissingObjectException expected) {
+ // Expected.
+ }
+ }
+ }
+
+ @Test
+ public void readBackFallsBackToRepo() throws Exception {
+ ObjectId blobId;
+ byte[] blob = Constants.encode("foo contents");
+ try (PackInserter ins = newInserter()) {
+ assertThat(blob.length, lessThan(ins.getBufferSize()));
+ blobId = ins.insert(OBJ_BLOB, blob);
+ ins.flush();
+ }
+
+ try (PackInserter ins = newInserter();
+ ObjectReader reader = ins.newReader()) {
+ assertBlob(reader, blobId, blob);
+ }
+ }
+
+ @Test
+ public void readBackSmallObjectBeforeLargeObject() throws Exception {
+ WindowCacheConfig wcc = new WindowCacheConfig();
+ wcc.setStreamFileThreshold(1024);
+ wcc.install();
+
+ ObjectId blobId1;
+ ObjectId blobId2;
+ ObjectId largeId;
+ byte[] blob1 = Constants.encode("blob1");
+ byte[] blob2 = Constants.encode("blob2");
+ byte[] largeBlob = newLargeBlob();
+ try (PackInserter ins = newInserter()) {
+ assertThat(blob1.length, lessThan(ins.getBufferSize()));
+ assertThat(largeBlob.length, greaterThan(ins.getBufferSize()));
+
+ blobId1 = ins.insert(OBJ_BLOB, blob1);
+ largeId = ins.insert(OBJ_BLOB, largeBlob);
+
+ try (ObjectReader reader = ins.newReader()) {
+ // A previous bug did not reset the file pointer to EOF after reading
+ // back. We need to seek to something further back than a full buffer,
+ // since the read-back code eagerly reads a full buffer's worth of data
+ // from the file to pass to the inflater. If we seeked back just a small
+ // amount, this step would consume the rest of the file, so the file
+ // pointer would coincidentally end up back at EOF, hiding the bug.
+ assertBlob(reader, blobId1, blob1);
+ }
+
+ blobId2 = ins.insert(OBJ_BLOB, blob2);
+
+ try (ObjectReader reader = ins.newReader()) {
+ assertBlob(reader, blobId1, blob1);
+ assertBlob(reader, blobId2, blob2);
+ assertBlob(reader, largeId, largeBlob);
+ }
+
+ ins.flush();
+ }
+
+ try (ObjectReader reader = db.newObjectReader()) {
+ assertBlob(reader, blobId1, blob1);
+ assertBlob(reader, blobId2, blob2);
+ assertBlob(reader, largeId, largeBlob);
+ }
+ }
+
+ private List<PackFile> listPacks() throws Exception {
+ List<PackFile> fromOpenDb = listPacks(db);
+ List<PackFile> reopened;
+ try (FileRepository db2 = new FileRepository(db.getDirectory())) {
+ reopened = listPacks(db2);
+ }
+ assertEquals(fromOpenDb.size(), reopened.size());
+ for (int i = 0 ; i < fromOpenDb.size(); i++) {
+ PackFile a = fromOpenDb.get(i);
+ PackFile b = reopened.get(i);
+ assertEquals(a.getPackName(), b.getPackName());
+ assertEquals(
+ a.getPackFile().getAbsolutePath(), b.getPackFile().getAbsolutePath());
+ assertEquals(a.getObjectCount(), b.getObjectCount());
+ a.getObjectCount();
+ }
+ return fromOpenDb;
+ }
+
+ private static List<PackFile> listPacks(FileRepository db) throws Exception {
+ return db.getObjectDatabase().getPacks().stream()
+ .sorted(comparing(PackFile::getPackName)).collect(toList());
+ }
+
+ private PackInserter newInserter() {
+ return db.getObjectDatabase().newPackInserter();
+ }
+
+ private static byte[] newLargeBlob() {
+ byte[] blob = new byte[10240];
+ new Random(0).nextBytes(blob);
+ return blob;
+ }
+
+ private static String getInode(File f) throws Exception {
+ BasicFileAttributes attrs = Files.readAttributes(
+ f.toPath(), BasicFileAttributes.class);
+ Object k = attrs.fileKey();
+ if (k == null) {
+ return null;
+ }
+ Pattern p = Pattern.compile("^\\(dev=[^,]*,ino=(\\d+)\\)$");
+ Matcher m = p.matcher(k.toString());
+ return m.matches() ? m.group(1) : null;
+ }
+
+ private static void assertBlob(ObjectReader reader, ObjectId id,
+ byte[] expected) throws Exception {
+ ObjectLoader loader = reader.open(id);
+ assertEquals(OBJ_BLOB, loader.getType());
+ assertEquals(expected.length, loader.getSize());
+ try (ObjectStream s = loader.openStream()) {
+ int n = (int) s.getSize();
+ byte[] actual = new byte[n];
+ assertEquals(n, IO.readFully(s, actual, 0));
+ assertArrayEquals(expected, actual);
+ }
+ }
+
+ private void assertPacksOnly() throws Exception {
+ new BadFileCollector(f -> !f.endsWith(".pack") && !f.endsWith(".idx"))
+ .assertNoBadFiles(db.getObjectDatabase().getDirectory());
+ }
+
+ private void assertNoObjects() throws Exception {
+ new BadFileCollector(f -> true)
+ .assertNoBadFiles(db.getObjectDatabase().getDirectory());
+ }
+
+ private static class BadFileCollector extends SimpleFileVisitor<Path> {
+ private final Predicate<String> badName;
+ private List<String> bad;
+
+ BadFileCollector(Predicate<String> badName) {
+ this.badName = badName;
+ }
+
+ void assertNoBadFiles(File f) throws IOException {
+ bad = new ArrayList<>();
+ Files.walkFileTree(f.toPath(), this);
+ if (!bad.isEmpty()) {
+ fail("unexpected files in object directory: " + bad);
+ }
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
+ String name = file.getFileName().toString();
+ if (!attrs.isDirectory() && badName.test(name)) {
+ bad.add(name);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
index 53db123..fefccf3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
@@ -61,32 +61,27 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.events.RefsChangedEvent;
import org.eclipse.jgit.events.RefsChangedListener;
import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.NullProgressMonitor;
-import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTag;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.ReceiveCommand;
-import org.eclipse.jgit.transport.ReceiveCommand.Type;
import org.junit.Before;
import org.junit.Test;
+@SuppressWarnings("boxing")
public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
private Repository diskRepo;
@@ -1293,125 +1288,20 @@ public void onRefsChanged(RefsChangedEvent event) {
}
@Test
- public void testBatchRefUpdateSimpleNoForce() throws IOException {
+ public void testPackedRefsLockFailure() throws Exception {
writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
- List<ReceiveCommand> commands = Arrays.asList(
- newCommand(A, B, "refs/heads/master",
- ReceiveCommand.Type.UPDATE),
- newCommand(B, A, "refs/heads/masters",
- ReceiveCommand.Type.UPDATE_NONFASTFORWARD));
- BatchRefUpdate batchUpdate = refdir.newBatchUpdate();
- batchUpdate.addCommand(commands);
- batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor());
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
- assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult());
- assertEquals(ReceiveCommand.Result.REJECTED_NONFASTFORWARD, commands
- .get(1).getResult());
- assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs
- .keySet().toString());
- assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId());
- assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId());
- }
-
- @Test
- public void testBatchRefUpdateSimpleForce() throws IOException {
- writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
- List<ReceiveCommand> commands = Arrays.asList(
- newCommand(A, B, "refs/heads/master",
- ReceiveCommand.Type.UPDATE),
- newCommand(B, A, "refs/heads/masters",
- ReceiveCommand.Type.UPDATE_NONFASTFORWARD));
- BatchRefUpdate batchUpdate = refdir.newBatchUpdate();
- batchUpdate.setAllowNonFastForwards(true);
- batchUpdate.addCommand(commands);
- batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor());
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
- assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult());
- assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult());
- assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs
- .keySet().toString());
- assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId());
- assertEquals(A.getId(), refs.get("refs/heads/masters").getObjectId());
- }
-
- @Test
- public void testBatchRefUpdateNonFastForwardDoesNotDoExpensiveMergeCheck()
- throws IOException {
- writeLooseRef("refs/heads/master", B);
- List<ReceiveCommand> commands = Arrays.asList(
- newCommand(B, A, "refs/heads/master",
- ReceiveCommand.Type.UPDATE_NONFASTFORWARD));
- BatchRefUpdate batchUpdate = refdir.newBatchUpdate();
- batchUpdate.setAllowNonFastForwards(true);
- batchUpdate.addCommand(commands);
- batchUpdate.execute(new RevWalk(diskRepo) {
- @Override
- public boolean isMergedInto(RevCommit base, RevCommit tip) {
- throw new AssertionError("isMergedInto() should not be called");
- }
- }, new StrictWorkMonitor());
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
- assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult());
- assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId());
- }
-
- @Test
- public void testBatchRefUpdateConflict() throws IOException {
- writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
- List<ReceiveCommand> commands = Arrays.asList(
- newCommand(A, B, "refs/heads/master",
- ReceiveCommand.Type.UPDATE),
- newCommand(null, A, "refs/heads/master/x",
- ReceiveCommand.Type.CREATE),
- newCommand(null, A, "refs/heads", ReceiveCommand.Type.CREATE));
- BatchRefUpdate batchUpdate = refdir.newBatchUpdate();
- batchUpdate.setAllowNonFastForwards(true);
- batchUpdate.addCommand(commands);
- batchUpdate
- .execute(new RevWalk(diskRepo), NullProgressMonitor.INSTANCE);
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
- assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult());
- assertEquals(ReceiveCommand.Result.LOCK_FAILURE, commands.get(1)
- .getResult());
- assertEquals(ReceiveCommand.Result.LOCK_FAILURE, commands.get(2)
- .getResult());
- assertEquals("[HEAD, refs/heads/master, refs/heads/masters]", refs
- .keySet().toString());
- assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId());
- assertEquals(B.getId(), refs.get("refs/heads/masters").getObjectId());
- }
-
- @Test
- public void testBatchRefUpdateConflictThanksToDelete() throws IOException {
- writeLooseRef("refs/heads/master", A);
- writeLooseRef("refs/heads/masters", B);
- List<ReceiveCommand> commands = Arrays.asList(
- newCommand(A, B, "refs/heads/master",
- ReceiveCommand.Type.UPDATE),
- newCommand(null, A, "refs/heads/masters/x",
- ReceiveCommand.Type.CREATE),
- newCommand(B, null, "refs/heads/masters",
- ReceiveCommand.Type.DELETE));
- BatchRefUpdate batchUpdate = refdir.newBatchUpdate();
- batchUpdate.setAllowNonFastForwards(true);
- batchUpdate.addCommand(commands);
- batchUpdate.execute(new RevWalk(diskRepo), new StrictWorkMonitor());
- Map<String, Ref> refs = refdir.getRefs(RefDatabase.ALL);
- assertEquals(ReceiveCommand.Result.OK, commands.get(0).getResult());
- assertEquals(ReceiveCommand.Result.OK, commands.get(1).getResult());
- assertEquals(ReceiveCommand.Result.OK, commands.get(2).getResult());
- assertEquals("[HEAD, refs/heads/master, refs/heads/masters/x]", refs
- .keySet().toString());
- assertEquals(A.getId(), refs.get("refs/heads/masters/x").getObjectId());
- }
-
- private static ReceiveCommand newCommand(RevCommit a, RevCommit b,
- String string, Type update) {
- return new ReceiveCommand(a != null ? a.getId() : null,
- b != null ? b.getId() : null, string, update);
+ refdir.setRetrySleepMs(Arrays.asList(0, 0));
+ LockFile myLock = refdir.lockPackedRefs();
+ try {
+ refdir.pack(Arrays.asList("refs/heads/master"));
+ fail("expected LockFailedException");
+ } catch (LockFailedException e) {
+ assertEquals(refdir.packedRefsFile.getPath(), e.getFile().getPath());
+ } finally {
+ myLock.unlock();
+ }
+ Ref ref = refdir.getRef("refs/heads/master");
+ assertEquals(Storage.LOOSE, ref.getStorage());
}
private void writeLooseRef(String name, AnyObjectId id) throws IOException {
@@ -1439,34 +1329,4 @@ private void deleteLooseRef(String name) {
File path = new File(diskRepo.getDirectory(), name);
assertTrue("deleted " + name, path.delete());
}
-
- private static final class StrictWorkMonitor implements ProgressMonitor {
- private int lastWork, totalWork;
-
- @Override
- public void start(int totalTasks) {
- // empty
- }
-
- @Override
- public void beginTask(String title, int total) {
- this.totalWork = total;
- lastWork = 0;
- }
-
- @Override
- public void update(int completed) {
- lastWork += completed;
- }
-
- @Override
- public void endTask() {
- assertEquals("Units of work recorded", totalWork, lastWork);
- }
-
- @Override
- public boolean isCancelled() {
- return false;
- }
- }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
index 0f26b0f..d8d45a8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
@@ -45,6 +45,7 @@
package org.eclipse.jgit.internal.storage.file;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.junit.Assert.assertEquals;
import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
import static org.junit.Assert.assertEquals;
@@ -65,6 +66,7 @@
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefRename;
@@ -241,14 +243,73 @@ public void testDeleteHead() throws IOException {
@Test
public void testDeleteHeadInBareRepo() throws IOException {
Repository bareRepo = createBareRepository();
+ String master = "refs/heads/master";
+ Ref head = bareRepo.exactRef(Constants.HEAD);
+ assertNotNull(head);
+ assertTrue(head.isSymbolic());
+ assertEquals(master, head.getLeaf().getName());
+ assertNull(head.getObjectId());
+ assertNull(bareRepo.exactRef(master));
+
+ ObjectId blobId;
+ try (ObjectInserter ins = bareRepo.newObjectInserter()) {
+ blobId = ins.insert(Constants.OBJ_BLOB, "contents".getBytes(UTF_8));
+ ins.flush();
+ }
+
+ // Create master via HEAD, so we delete it.
RefUpdate ref = bareRepo.updateRef(Constants.HEAD);
- ref.setNewObjectId(ObjectId
- .fromString("0123456789012345678901234567890123456789"));
- // Create the HEAD ref so we can delete it.
+ ref.setNewObjectId(blobId);
assertEquals(Result.NEW, ref.update());
+
+ head = bareRepo.exactRef(Constants.HEAD);
+ assertTrue(head.isSymbolic());
+ assertEquals(master, head.getLeaf().getName());
+ assertEquals(blobId, head.getLeaf().getObjectId());
+ assertEquals(blobId, bareRepo.exactRef(master).getObjectId());
+
+ // Unlike in a non-bare repo, deleting the HEAD is allowed, and leaves HEAD
+ // back in a dangling state.
ref = bareRepo.updateRef(Constants.HEAD);
- delete(bareRepo, ref, Result.NO_CHANGE, true, true);
+ ref.setExpectedOldObjectId(blobId);
+ ref.setForceUpdate(true);
+ delete(bareRepo, ref, Result.FORCED, true, true);
+
+ head = bareRepo.exactRef(Constants.HEAD);
+ assertNotNull(head);
+ assertTrue(head.isSymbolic());
+ assertEquals(master, head.getLeaf().getName());
+ assertNull(head.getObjectId());
+ assertNull(bareRepo.exactRef(master));
}
+
+ @Test
+ public void testDeleteSymref() throws IOException {
+ RefUpdate dst = updateRef("refs/heads/abc");
+ assertEquals(Result.NEW, dst.update());
+ ObjectId id = dst.getNewObjectId();
+
+ RefUpdate u = db.updateRef("refs/symref");
+ assertEquals(Result.NEW, u.link(dst.getName()));
+
+ Ref ref = db.exactRef(u.getName());
+ assertNotNull(ref);
+ assertTrue(ref.isSymbolic());
+ assertEquals(dst.getName(), ref.getLeaf().getName());
+ assertEquals(id, ref.getLeaf().getObjectId());
+
+ u = db.updateRef(u.getName());
+ u.setDetachingSymbolicRef();
+ u.setForceUpdate(true);
+ assertEquals(Result.FORCED, u.delete());
+
+ assertNull(db.exactRef(u.getName()));
+ ref = db.exactRef(dst.getName());
+ assertNotNull(ref);
+ assertFalse(ref.isSymbolic());
+ assertEquals(id, ref.getObjectId());
+ }
+
/**
* Delete a loose ref and make sure the directory in refs is deleted too,
* and the reflog dir too
@@ -899,12 +960,66 @@ public void testRenameRefNameColission2avoided() throws IOException {
"HEAD").getReverseEntries().get(0).getComment());
}
+ @Test
+ public void testCreateMissingObject() throws IOException {
+ String name = "refs/heads/abc";
+ ObjectId bad =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ RefUpdate ru = db.updateRef(name);
+ ru.setNewObjectId(bad);
+ Result update = ru.update();
+ assertEquals(Result.REJECTED_MISSING_OBJECT, update);
+
+ Ref ref = db.exactRef(name);
+ assertNull(ref);
+ }
+
+ @Test
+ public void testUpdateMissingObject() throws IOException {
+ String name = "refs/heads/abc";
+ RefUpdate ru = updateRef(name);
+ Result update = ru.update();
+ assertEquals(Result.NEW, update);
+ ObjectId oldId = ru.getNewObjectId();
+
+ ObjectId bad =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ ru = db.updateRef(name);
+ ru.setNewObjectId(bad);
+ update = ru.update();
+ assertEquals(Result.REJECTED_MISSING_OBJECT, update);
+
+ Ref ref = db.exactRef(name);
+ assertNotNull(ref);
+ assertEquals(oldId, ref.getObjectId());
+ }
+
+ @Test
+ public void testForceUpdateMissingObject() throws IOException {
+ String name = "refs/heads/abc";
+ RefUpdate ru = updateRef(name);
+ Result update = ru.update();
+ assertEquals(Result.NEW, update);
+ ObjectId oldId = ru.getNewObjectId();
+
+ ObjectId bad =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ ru = db.updateRef(name);
+ ru.setNewObjectId(bad);
+ update = ru.forceUpdate();
+ assertEquals(Result.REJECTED_MISSING_OBJECT, update);
+
+ Ref ref = db.exactRef(name);
+ assertNotNull(ref);
+ assertEquals(oldId, ref.getObjectId());
+ }
+
private static void writeReflog(Repository db, ObjectId newId, String msg,
String refName) throws IOException {
RefDirectory refs = (RefDirectory) db.getRefDatabase();
RefDirectoryUpdate update = refs.newUpdate(refName, true);
update.setNewObjectId(newId);
- refs.log(update, msg, true);
+ refs.log(false, update, msg, true);
}
private static class SubclassedId extends ObjectId {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
index 89b969e..7f40bae 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
@@ -61,7 +61,8 @@ public class ReflogWriterTest extends SampleDataRepositoryTestCase {
@Test
public void shouldFilterLineFeedFromMessage() throws Exception {
- ReflogWriter writer = new ReflogWriter(db);
+ ReflogWriter writer =
+ new ReflogWriter((RefDirectory) db.getRefDatabase());
PersonIdent ident = new PersonIdent("John Doe", "john@doe.com",
1243028200000L, 120);
ObjectId oldId = ObjectId
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
index ae1e531..9d23d83 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
@@ -661,33 +661,39 @@ public void test027_UnpackedRefHigherPriorityThanPacked()
@Test
public void test028_LockPackedRef() throws IOException {
+ ObjectId id1;
+ ObjectId id2;
+ try (ObjectInserter ins = db.newObjectInserter()) {
+ id1 = ins.insert(
+ Constants.OBJ_BLOB, "contents1".getBytes(Constants.CHARSET));
+ id2 = ins.insert(
+ Constants.OBJ_BLOB, "contents2".getBytes(Constants.CHARSET));
+ ins.flush();
+ }
+
writeTrashFile(".git/packed-refs",
- "7f822839a2fe9760f386cbbbcb3f92c5fe81def7 refs/heads/foobar");
+ id1.name() + " refs/heads/foobar");
writeTrashFile(".git/HEAD", "ref: refs/heads/foobar\n");
BUG_WorkAroundRacyGitIssues("packed-refs");
BUG_WorkAroundRacyGitIssues("HEAD");
ObjectId resolve = db.resolve("HEAD");
- assertEquals("7f822839a2fe9760f386cbbbcb3f92c5fe81def7", resolve.name());
+ assertEquals(id1, resolve);
RefUpdate lockRef = db.updateRef("HEAD");
- ObjectId newId = ObjectId
- .fromString("07f822839a2fe9760f386cbbbcb3f92c5fe81def");
- lockRef.setNewObjectId(newId);
+ lockRef.setNewObjectId(id2);
assertEquals(RefUpdate.Result.FORCED, lockRef.forceUpdate());
assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists());
- assertEquals(newId, db.resolve("refs/heads/foobar"));
+ assertEquals(id2, db.resolve("refs/heads/foobar"));
// Again. The ref already exists
RefUpdate lockRef2 = db.updateRef("HEAD");
- ObjectId newId2 = ObjectId
- .fromString("7f822839a2fe9760f386cbbbcb3f92c5fe81def7");
- lockRef2.setNewObjectId(newId2);
+ lockRef2.setNewObjectId(id1);
assertEquals(RefUpdate.Result.FORCED, lockRef2.forceUpdate());
assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists());
- assertEquals(newId2, db.resolve("refs/heads/foobar"));
+ assertEquals(id1, db.resolve("refs/heads/foobar"));
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java
new file mode 100644
index 0000000..adba048
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.reftable;
+
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.internal.storage.io.BlockSource;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefComparator;
+import org.junit.Test;
+
+public class MergedReftableTest {
+ @Test
+ public void noTables() throws IOException {
+ MergedReftable mr = merge(new byte[0][]);
+ try (RefCursor rc = mr.allRefs()) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = mr.seekRef(HEAD)) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = mr.seekRef(R_HEADS)) {
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void oneEmptyTable() throws IOException {
+ MergedReftable mr = merge(write());
+ try (RefCursor rc = mr.allRefs()) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = mr.seekRef(HEAD)) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = mr.seekRef(R_HEADS)) {
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void twoEmptyTables() throws IOException {
+ MergedReftable mr = merge(write(), write());
+ try (RefCursor rc = mr.allRefs()) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = mr.seekRef(HEAD)) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = mr.seekRef(R_HEADS)) {
+ assertFalse(rc.next());
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void oneTableScan() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 567; i++) {
+ refs.add(ref(String.format("refs/heads/%03d", i), i));
+ }
+
+ MergedReftable mr = merge(write(refs));
+ try (RefCursor rc = mr.allRefs()) {
+ for (Ref exp : refs) {
+ assertTrue("has " + exp.getName(), rc.next());
+ Ref act = rc.getRef();
+ assertEquals(exp.getName(), act.getName());
+ assertEquals(exp.getObjectId(), act.getObjectId());
+ }
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void deleteIsHidden() throws IOException {
+ List<Ref> delta1 = Arrays.asList(
+ ref("refs/heads/apple", 1),
+ ref("refs/heads/master", 2));
+ List<Ref> delta2 = Arrays.asList(delete("refs/heads/apple"));
+
+ MergedReftable mr = merge(write(delta1), write(delta2));
+ try (RefCursor rc = mr.allRefs()) {
+ assertTrue(rc.next());
+ assertEquals("refs/heads/master", rc.getRef().getName());
+ assertEquals(id(2), rc.getRef().getObjectId());
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void twoTableSeek() throws IOException {
+ List<Ref> delta1 = Arrays.asList(
+ ref("refs/heads/apple", 1),
+ ref("refs/heads/master", 2));
+ List<Ref> delta2 = Arrays.asList(ref("refs/heads/banana", 3));
+
+ MergedReftable mr = merge(write(delta1), write(delta2));
+ try (RefCursor rc = mr.seekRef("refs/heads/master")) {
+ assertTrue(rc.next());
+ assertEquals("refs/heads/master", rc.getRef().getName());
+ assertEquals(id(2), rc.getRef().getObjectId());
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void twoTableById() throws IOException {
+ List<Ref> delta1 = Arrays.asList(
+ ref("refs/heads/apple", 1),
+ ref("refs/heads/master", 2));
+ List<Ref> delta2 = Arrays.asList(ref("refs/heads/banana", 3));
+
+ MergedReftable mr = merge(write(delta1), write(delta2));
+ try (RefCursor rc = mr.byObjectId(id(2))) {
+ assertTrue(rc.next());
+ assertEquals("refs/heads/master", rc.getRef().getName());
+ assertEquals(id(2), rc.getRef().getObjectId());
+ assertFalse(rc.next());
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void fourTableScan() throws IOException {
+ List<Ref> base = new ArrayList<>();
+ for (int i = 1; i <= 567; i++) {
+ base.add(ref(String.format("refs/heads/%03d", i), i));
+ }
+
+ List<Ref> delta1 = Arrays.asList(
+ ref("refs/heads/next", 4),
+ ref(String.format("refs/heads/%03d", 55), 4096));
+ List<Ref> delta2 = Arrays.asList(
+ delete("refs/heads/next"),
+ ref(String.format("refs/heads/%03d", 55), 8192));
+ List<Ref> delta3 = Arrays.asList(
+ ref("refs/heads/master", 4242),
+ ref(String.format("refs/heads/%03d", 42), 5120),
+ ref(String.format("refs/heads/%03d", 98), 6120));
+
+ List<Ref> expected = merge(base, delta1, delta2, delta3);
+ MergedReftable mr = merge(
+ write(base),
+ write(delta1),
+ write(delta2),
+ write(delta3));
+ try (RefCursor rc = mr.allRefs()) {
+ for (Ref exp : expected) {
+ assertTrue("has " + exp.getName(), rc.next());
+ Ref act = rc.getRef();
+ assertEquals(exp.getName(), act.getName());
+ assertEquals(exp.getObjectId(), act.getObjectId());
+ }
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void scanIncludeDeletes() throws IOException {
+ List<Ref> delta1 = Arrays.asList(ref("refs/heads/next", 4));
+ List<Ref> delta2 = Arrays.asList(delete("refs/heads/next"));
+ List<Ref> delta3 = Arrays.asList(ref("refs/heads/master", 8));
+
+ MergedReftable mr = merge(write(delta1), write(delta2), write(delta3));
+ mr.setIncludeDeletes(true);
+ try (RefCursor rc = mr.allRefs()) {
+ assertTrue(rc.next());
+ Ref r = rc.getRef();
+ assertEquals("refs/heads/master", r.getName());
+ assertEquals(id(8), r.getObjectId());
+
+ assertTrue(rc.next());
+ r = rc.getRef();
+ assertEquals("refs/heads/next", r.getName());
+ assertEquals(NEW, r.getStorage());
+ assertNull(r.getObjectId());
+
+ assertFalse(rc.next());
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void oneTableSeek() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 567; i++) {
+ refs.add(ref(String.format("refs/heads/%03d", i), i));
+ }
+
+ MergedReftable mr = merge(write(refs));
+ for (Ref exp : refs) {
+ try (RefCursor rc = mr.seekRef(exp.getName())) {
+ assertTrue("has " + exp.getName(), rc.next());
+ Ref act = rc.getRef();
+ assertEquals(exp.getName(), act.getName());
+ assertEquals(exp.getObjectId(), act.getObjectId());
+ assertFalse(rc.next());
+ }
+ }
+ }
+
+ @Test
+ public void missedUpdate() throws IOException {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ ReftableWriter writer = new ReftableWriter()
+ .setMinUpdateIndex(1)
+ .setMaxUpdateIndex(3)
+ .begin(buf);
+ writer.writeRef(ref("refs/heads/a", 1), 1);
+ writer.writeRef(ref("refs/heads/c", 3), 3);
+ writer.finish();
+ byte[] base = buf.toByteArray();
+
+ byte[] delta = write(Arrays.asList(
+ ref("refs/heads/b", 2),
+ ref("refs/heads/c", 4)),
+ 2);
+ MergedReftable mr = merge(base, delta);
+ try (RefCursor rc = mr.allRefs()) {
+ assertTrue(rc.next());
+ assertEquals("refs/heads/a", rc.getRef().getName());
+ assertEquals(id(1), rc.getRef().getObjectId());
+ assertEquals(1, rc.getUpdateIndex());
+
+ assertTrue(rc.next());
+ assertEquals("refs/heads/b", rc.getRef().getName());
+ assertEquals(id(2), rc.getRef().getObjectId());
+ assertEquals(2, rc.getUpdateIndex());
+
+ assertTrue(rc.next());
+ assertEquals("refs/heads/c", rc.getRef().getName());
+ assertEquals(id(3), rc.getRef().getObjectId());
+ assertEquals(3, rc.getUpdateIndex());
+ }
+ }
+
+ @Test
+ public void compaction() throws IOException {
+ List<Ref> delta1 = Arrays.asList(
+ ref("refs/heads/next", 4),
+ ref("refs/heads/master", 1));
+ List<Ref> delta2 = Arrays.asList(delete("refs/heads/next"));
+ List<Ref> delta3 = Arrays.asList(ref("refs/heads/master", 8));
+
+ ReftableCompactor compactor = new ReftableCompactor();
+ compactor.addAll(Arrays.asList(
+ read(write(delta1)),
+ read(write(delta2)),
+ read(write(delta3))));
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ compactor.compact(out);
+ byte[] table = out.toByteArray();
+
+ ReftableReader reader = read(table);
+ try (RefCursor rc = reader.allRefs()) {
+ assertTrue(rc.next());
+ Ref r = rc.getRef();
+ assertEquals("refs/heads/master", r.getName());
+ assertEquals(id(8), r.getObjectId());
+ assertFalse(rc.next());
+ }
+ }
+
+ private static MergedReftable merge(byte[]... table) {
+ List<Reftable> stack = new ArrayList<>(table.length);
+ for (byte[] b : table) {
+ stack.add(read(b));
+ }
+ return new MergedReftable(stack);
+ }
+
+ private static ReftableReader read(byte[] table) {
+ return new ReftableReader(BlockSource.from(table));
+ }
+
+ private static Ref ref(String name, int id) {
+ return new ObjectIdRef.PeeledNonTag(PACKED, name, id(id));
+ }
+
+ private static Ref delete(String name) {
+ return new ObjectIdRef.Unpeeled(NEW, name, null);
+ }
+
+ private static ObjectId id(int i) {
+ byte[] buf = new byte[OBJECT_ID_LENGTH];
+ buf[0] = (byte) (i & 0xff);
+ buf[1] = (byte) ((i >>> 8) & 0xff);
+ buf[2] = (byte) ((i >>> 16) & 0xff);
+ buf[3] = (byte) (i >>> 24);
+ return ObjectId.fromRaw(buf);
+ }
+
+ private byte[] write(Ref... refs) throws IOException {
+ return write(Arrays.asList(refs));
+ }
+
+ private byte[] write(Collection<Ref> refs) throws IOException {
+ return write(refs, 1);
+ }
+
+ private byte[] write(Collection<Ref> refs, long updateIndex)
+ throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ new ReftableWriter()
+ .setMinUpdateIndex(updateIndex)
+ .setMaxUpdateIndex(updateIndex)
+ .begin(buffer)
+ .sortAndWriteRefs(refs)
+ .finish();
+ return buffer.toByteArray();
+ }
+
+ @SafeVarargs
+ private static List<Ref> merge(List<Ref>... tables) {
+ Map<String, Ref> expect = new HashMap<>();
+ for (List<Ref> t : tables) {
+ for (Ref r : t) {
+ if (r.getStorage() == NEW && r.getObjectId() == null) {
+ expect.remove(r.getName());
+ } else {
+ expect.put(r.getName(), r);
+ }
+ }
+ }
+
+ List<Ref> expected = new ArrayList<>(expect.values());
+ Collections.sort(expected, RefComparator.INSTANCE);
+ return expected;
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java
new file mode 100644
index 0000000..3ea3061
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java
@@ -0,0 +1,726 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.reftable;
+
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.io.BlockSource;
+import org.eclipse.jgit.internal.storage.reftable.ReftableWriter.Stats;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.junit.Test;
+
+public class ReftableTest {
+ private static final String MASTER = "refs/heads/master";
+ private static final String NEXT = "refs/heads/next";
+ private static final String V1_0 = "refs/tags/v1.0";
+
+ private Stats stats;
+
+ @Test
+ public void emptyTable() throws IOException {
+ byte[] table = write();
+ assertEquals(92 /* header, footer */, table.length);
+ assertEquals('R', table[0]);
+ assertEquals('E', table[1]);
+ assertEquals('F', table[2]);
+ assertEquals('T', table[3]);
+ assertEquals(0x01, table[4]);
+ assertTrue(ReftableConstants.isFileHeaderMagic(table, 0, 8));
+ assertTrue(ReftableConstants.isFileHeaderMagic(table, 24, 92));
+
+ Reftable t = read(table);
+ try (RefCursor rc = t.allRefs()) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef(HEAD)) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef(R_HEADS)) {
+ assertFalse(rc.next());
+ }
+ try (LogCursor rc = t.allLogs()) {
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void emptyVirtualTableFromRefs() throws IOException {
+ Reftable t = Reftable.from(Collections.emptyList());
+ try (RefCursor rc = t.allRefs()) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef(HEAD)) {
+ assertFalse(rc.next());
+ }
+ try (LogCursor rc = t.allLogs()) {
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void estimateCurrentBytesOneRef() throws IOException {
+ Ref exp = ref(MASTER, 1);
+ int expBytes = 24 + 4 + 5 + 4 + MASTER.length() + 20 + 68;
+
+ byte[] table;
+ ReftableConfig cfg = new ReftableConfig();
+ cfg.setIndexObjects(false);
+ ReftableWriter writer = new ReftableWriter().setConfig(cfg);
+ try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
+ writer.begin(buf);
+ assertEquals(92, writer.estimateTotalBytes());
+ writer.writeRef(exp);
+ assertEquals(expBytes, writer.estimateTotalBytes());
+ writer.finish();
+ table = buf.toByteArray();
+ }
+ assertEquals(expBytes, table.length);
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void estimateCurrentBytesWithIndex() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 5670; i++) {
+ refs.add(ref(String.format("refs/heads/%04d", i), i));
+ }
+
+ ReftableConfig cfg = new ReftableConfig();
+ cfg.setIndexObjects(false);
+ cfg.setMaxIndexLevels(1);
+
+ int expBytes = 147860;
+ byte[] table;
+ ReftableWriter writer = new ReftableWriter().setConfig(cfg);
+ try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
+ writer.begin(buf);
+ writer.sortAndWriteRefs(refs);
+ assertEquals(expBytes, writer.estimateTotalBytes());
+ writer.finish();
+ stats = writer.getStats();
+ table = buf.toByteArray();
+ }
+ assertEquals(1, stats.refIndexLevels());
+ assertEquals(expBytes, table.length);
+ }
+
+ @Test
+ public void oneIdRef() throws IOException {
+ Ref exp = ref(MASTER, 1);
+ byte[] table = write(exp);
+ assertEquals(24 + 4 + 5 + 4 + MASTER.length() + 20 + 68, table.length);
+
+ ReftableReader t = read(table);
+ try (RefCursor rc = t.allRefs()) {
+ assertTrue(rc.next());
+ Ref act = rc.getRef();
+ assertNotNull(act);
+ assertEquals(PACKED, act.getStorage());
+ assertTrue(act.isPeeled());
+ assertFalse(act.isSymbolic());
+ assertEquals(exp.getName(), act.getName());
+ assertEquals(exp.getObjectId(), act.getObjectId());
+ assertNull(act.getPeeledObjectId());
+ assertFalse(rc.wasDeleted());
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef(MASTER)) {
+ assertTrue(rc.next());
+ Ref act = rc.getRef();
+ assertNotNull(act);
+ assertEquals(exp.getName(), act.getName());
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void oneTagRef() throws IOException {
+ Ref exp = tag(V1_0, 1, 2);
+ byte[] table = write(exp);
+ assertEquals(24 + 4 + 5 + 3 + V1_0.length() + 40 + 68, table.length);
+
+ ReftableReader t = read(table);
+ try (RefCursor rc = t.allRefs()) {
+ assertTrue(rc.next());
+ Ref act = rc.getRef();
+ assertNotNull(act);
+ assertEquals(PACKED, act.getStorage());
+ assertTrue(act.isPeeled());
+ assertFalse(act.isSymbolic());
+ assertEquals(exp.getName(), act.getName());
+ assertEquals(exp.getObjectId(), act.getObjectId());
+ assertEquals(exp.getPeeledObjectId(), act.getPeeledObjectId());
+ }
+ }
+
+ @Test
+ public void oneSymbolicRef() throws IOException {
+ Ref exp = sym(HEAD, MASTER);
+ byte[] table = write(exp);
+ assertEquals(
+ 24 + 4 + 5 + 2 + HEAD.length() + 2 + MASTER.length() + 68,
+ table.length);
+
+ ReftableReader t = read(table);
+ try (RefCursor rc = t.allRefs()) {
+ assertTrue(rc.next());
+ Ref act = rc.getRef();
+ assertNotNull(act);
+ assertTrue(act.isSymbolic());
+ assertEquals(exp.getName(), act.getName());
+ assertNotNull(act.getLeaf());
+ assertEquals(MASTER, act.getTarget().getName());
+ assertNull(act.getObjectId());
+ }
+ }
+
+ @Test
+ public void resolveSymbolicRef() throws IOException {
+ Reftable t = read(write(
+ sym(HEAD, "refs/heads/tmp"),
+ sym("refs/heads/tmp", MASTER),
+ ref(MASTER, 1)));
+
+ Ref head = t.exactRef(HEAD);
+ assertNull(head.getObjectId());
+ assertEquals("refs/heads/tmp", head.getTarget().getName());
+
+ head = t.resolve(head);
+ assertNotNull(head);
+ assertEquals(id(1), head.getObjectId());
+
+ Ref master = t.exactRef(MASTER);
+ assertNotNull(master);
+ assertSame(master, t.resolve(master));
+ }
+
+ @Test
+ public void failDeepChainOfSymbolicRef() throws IOException {
+ Reftable t = read(write(
+ sym(HEAD, "refs/heads/1"),
+ sym("refs/heads/1", "refs/heads/2"),
+ sym("refs/heads/2", "refs/heads/3"),
+ sym("refs/heads/3", "refs/heads/4"),
+ sym("refs/heads/4", "refs/heads/5"),
+ sym("refs/heads/5", MASTER),
+ ref(MASTER, 1)));
+
+ Ref head = t.exactRef(HEAD);
+ assertNull(head.getObjectId());
+ assertNull(t.resolve(head));
+ }
+
+ @Test
+ public void oneDeletedRef() throws IOException {
+ String name = "refs/heads/gone";
+ Ref exp = newRef(name);
+ byte[] table = write(exp);
+ assertEquals(24 + 4 + 5 + 3 + name.length() + 68, table.length);
+
+ ReftableReader t = read(table);
+ try (RefCursor rc = t.allRefs()) {
+ assertFalse(rc.next());
+ }
+
+ t.setIncludeDeletes(true);
+ try (RefCursor rc = t.allRefs()) {
+ assertTrue(rc.next());
+ Ref act = rc.getRef();
+ assertNotNull(act);
+ assertFalse(act.isSymbolic());
+ assertEquals(name, act.getName());
+ assertEquals(NEW, act.getStorage());
+ assertNull(act.getObjectId());
+ assertTrue(rc.wasDeleted());
+ }
+ }
+
+ @Test
+ public void seekNotFound() throws IOException {
+ Ref exp = ref(MASTER, 1);
+ ReftableReader t = read(write(exp));
+ try (RefCursor rc = t.seekRef("refs/heads/a")) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef("refs/heads/n")) {
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void namespaceNotFound() throws IOException {
+ Ref exp = ref(MASTER, 1);
+ ReftableReader t = read(write(exp));
+ try (RefCursor rc = t.seekRef("refs/changes/")) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef("refs/tags/")) {
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void namespaceHeads() throws IOException {
+ Ref master = ref(MASTER, 1);
+ Ref next = ref(NEXT, 2);
+ Ref v1 = tag(V1_0, 3, 4);
+
+ ReftableReader t = read(write(master, next, v1));
+ try (RefCursor rc = t.seekRef("refs/tags/")) {
+ assertTrue(rc.next());
+ assertEquals(V1_0, rc.getRef().getName());
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef("refs/heads/")) {
+ assertTrue(rc.next());
+ assertEquals(MASTER, rc.getRef().getName());
+
+ assertTrue(rc.next());
+ assertEquals(NEXT, rc.getRef().getName());
+
+ assertFalse(rc.next());
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void indexScan() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 5670; i++) {
+ refs.add(ref(String.format("refs/heads/%04d", i), i));
+ }
+
+ byte[] table = write(refs);
+ assertTrue(stats.refIndexLevels() > 0);
+ assertTrue(stats.refIndexSize() > 0);
+ assertScan(refs, read(table));
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void indexSeek() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 5670; i++) {
+ refs.add(ref(String.format("refs/heads/%04d", i), i));
+ }
+
+ byte[] table = write(refs);
+ assertTrue(stats.refIndexLevels() > 0);
+ assertTrue(stats.refIndexSize() > 0);
+ assertSeek(refs, read(table));
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void noIndexScan() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 567; i++) {
+ refs.add(ref(String.format("refs/heads/%03d", i), i));
+ }
+
+ byte[] table = write(refs);
+ assertEquals(0, stats.refIndexLevels());
+ assertEquals(0, stats.refIndexSize());
+ assertEquals(table.length, stats.totalBytes());
+ assertScan(refs, read(table));
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void noIndexSeek() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 567; i++) {
+ refs.add(ref(String.format("refs/heads/%03d", i), i));
+ }
+
+ byte[] table = write(refs);
+ assertEquals(0, stats.refIndexLevels());
+ assertSeek(refs, read(table));
+ }
+
+ @Test
+ public void withReflog() throws IOException {
+ Ref master = ref(MASTER, 1);
+ Ref next = ref(NEXT, 2);
+ PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+ String msg = "test";
+
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ ReftableWriter writer = new ReftableWriter()
+ .setMinUpdateIndex(1)
+ .setMaxUpdateIndex(1)
+ .begin(buffer);
+
+ writer.writeRef(master);
+ writer.writeRef(next);
+
+ writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg);
+ writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(2), msg);
+
+ writer.finish();
+ byte[] table = buffer.toByteArray();
+ assertEquals(247, table.length);
+
+ ReftableReader t = read(table);
+ try (RefCursor rc = t.allRefs()) {
+ assertTrue(rc.next());
+ assertEquals(MASTER, rc.getRef().getName());
+ assertEquals(id(1), rc.getRef().getObjectId());
+ assertEquals(1, rc.getUpdateIndex());
+
+ assertTrue(rc.next());
+ assertEquals(NEXT, rc.getRef().getName());
+ assertEquals(id(2), rc.getRef().getObjectId());
+ assertFalse(rc.next());
+ }
+ try (LogCursor lc = t.allLogs()) {
+ assertTrue(lc.next());
+ assertEquals(MASTER, lc.getRefName());
+ assertEquals(1, lc.getUpdateIndex());
+ assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
+ assertEquals(id(1), lc.getReflogEntry().getNewId());
+ assertEquals(who, lc.getReflogEntry().getWho());
+ assertEquals(msg, lc.getReflogEntry().getComment());
+
+ assertTrue(lc.next());
+ assertEquals(NEXT, lc.getRefName());
+ assertEquals(1, lc.getUpdateIndex());
+ assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
+ assertEquals(id(2), lc.getReflogEntry().getNewId());
+ assertEquals(who, lc.getReflogEntry().getWho());
+ assertEquals(msg, lc.getReflogEntry().getComment());
+
+ assertFalse(lc.next());
+ }
+ }
+
+ @Test
+ public void onlyReflog() throws IOException {
+ PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+ String msg = "test";
+
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ ReftableWriter writer = new ReftableWriter()
+ .setMinUpdateIndex(1)
+ .setMaxUpdateIndex(1)
+ .begin(buffer);
+ writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg);
+ writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(2), msg);
+ writer.finish();
+ byte[] table = buffer.toByteArray();
+ stats = writer.getStats();
+ assertEquals(170, table.length);
+ assertEquals(0, stats.refCount());
+ assertEquals(0, stats.refBytes());
+ assertEquals(0, stats.refIndexLevels());
+
+ ReftableReader t = read(table);
+ try (RefCursor rc = t.allRefs()) {
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.seekRef("refs/heads/")) {
+ assertFalse(rc.next());
+ }
+ try (LogCursor lc = t.allLogs()) {
+ assertTrue(lc.next());
+ assertEquals(MASTER, lc.getRefName());
+ assertEquals(1, lc.getUpdateIndex());
+ assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
+ assertEquals(id(1), lc.getReflogEntry().getNewId());
+ assertEquals(who, lc.getReflogEntry().getWho());
+ assertEquals(msg, lc.getReflogEntry().getComment());
+
+ assertTrue(lc.next());
+ assertEquals(NEXT, lc.getRefName());
+ assertEquals(1, lc.getUpdateIndex());
+ assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
+ assertEquals(id(2), lc.getReflogEntry().getNewId());
+ assertEquals(who, lc.getReflogEntry().getWho());
+ assertEquals(msg, lc.getReflogEntry().getComment());
+
+ assertFalse(lc.next());
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void logScan() throws IOException {
+ ReftableConfig cfg = new ReftableConfig();
+ cfg.setRefBlockSize(256);
+ cfg.setLogBlockSize(2048);
+
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ ReftableWriter writer = new ReftableWriter(cfg);
+ writer.setMinUpdateIndex(1).setMaxUpdateIndex(1).begin(buffer);
+
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 5670; i++) {
+ Ref ref = ref(String.format("refs/heads/%03d", i), i);
+ refs.add(ref);
+ writer.writeRef(ref);
+ }
+
+ PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+ for (Ref ref : refs) {
+ writer.writeLog(ref.getName(), 1, who,
+ ObjectId.zeroId(), ref.getObjectId(),
+ "create " + ref.getName());
+ }
+ writer.finish();
+ stats = writer.getStats();
+ assertTrue(stats.logBytes() > 4096);
+ byte[] table = buffer.toByteArray();
+
+ ReftableReader t = read(table);
+ try (LogCursor lc = t.allLogs()) {
+ for (Ref exp : refs) {
+ assertTrue("has " + exp.getName(), lc.next());
+ assertEquals(exp.getName(), lc.getRefName());
+ ReflogEntry entry = lc.getReflogEntry();
+ assertNotNull(entry);
+ assertEquals(who, entry.getWho());
+ assertEquals(ObjectId.zeroId(), entry.getOldId());
+ assertEquals(exp.getObjectId(), entry.getNewId());
+ assertEquals("create " + exp.getName(), entry.getComment());
+ }
+ assertFalse(lc.next());
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void byObjectIdOneRefNoIndex() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 200; i++) {
+ refs.add(ref(String.format("refs/heads/%02d", i), i));
+ }
+ refs.add(ref("refs/heads/master", 100));
+
+ ReftableReader t = read(write(refs));
+ assertEquals(0, stats.objIndexSize());
+
+ try (RefCursor rc = t.byObjectId(id(42))) {
+ assertTrue("has 42", rc.next());
+ assertEquals("refs/heads/42", rc.getRef().getName());
+ assertEquals(id(42), rc.getRef().getObjectId());
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.byObjectId(id(100))) {
+ assertTrue("has 100", rc.next());
+ assertEquals("refs/heads/100", rc.getRef().getName());
+ assertEquals(id(100), rc.getRef().getObjectId());
+
+ assertTrue("has master", rc.next());
+ assertEquals("refs/heads/master", rc.getRef().getName());
+ assertEquals(id(100), rc.getRef().getObjectId());
+
+ assertFalse(rc.next());
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void byObjectIdOneRefWithIndex() throws IOException {
+ List<Ref> refs = new ArrayList<>();
+ for (int i = 1; i <= 5200; i++) {
+ refs.add(ref(String.format("refs/heads/%02d", i), i));
+ }
+ refs.add(ref("refs/heads/master", 100));
+
+ ReftableReader t = read(write(refs));
+ assertTrue(stats.objIndexSize() > 0);
+
+ try (RefCursor rc = t.byObjectId(id(42))) {
+ assertTrue("has 42", rc.next());
+ assertEquals("refs/heads/42", rc.getRef().getName());
+ assertEquals(id(42), rc.getRef().getObjectId());
+ assertFalse(rc.next());
+ }
+ try (RefCursor rc = t.byObjectId(id(100))) {
+ assertTrue("has 100", rc.next());
+ assertEquals("refs/heads/100", rc.getRef().getName());
+ assertEquals(id(100), rc.getRef().getObjectId());
+
+ assertTrue("has master", rc.next());
+ assertEquals("refs/heads/master", rc.getRef().getName());
+ assertEquals(id(100), rc.getRef().getObjectId());
+
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void unpeeledDoesNotWrite() {
+ try {
+ write(new ObjectIdRef.Unpeeled(PACKED, MASTER, id(1)));
+ fail("expected IOException");
+ } catch (IOException e) {
+ assertEquals(JGitText.get().peeledRefIsRequired, e.getMessage());
+ }
+ }
+
+ @Test
+ public void nameTooLongDoesNotWrite() throws IOException {
+ try {
+ ReftableConfig cfg = new ReftableConfig();
+ cfg.setRefBlockSize(64);
+
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ ReftableWriter writer = new ReftableWriter(cfg).begin(buffer);
+ writer.writeRef(ref("refs/heads/i-am-not-a-teapot", 1));
+ writer.finish();
+ fail("expected BlockSizeTooSmallException");
+ } catch (BlockSizeTooSmallException e) {
+ assertEquals(85, e.getMinimumBlockSize());
+ }
+ }
+
+ @Test
+ public void badCrc32() throws IOException {
+ byte[] table = write();
+ table[table.length - 1] = 0x42;
+
+ try {
+ read(table).seekRef(HEAD);
+ fail("expected IOException");
+ } catch (IOException e) {
+ assertEquals(JGitText.get().invalidReftableCRC, e.getMessage());
+ }
+ }
+
+
+ private static void assertScan(List<Ref> refs, Reftable t)
+ throws IOException {
+ try (RefCursor rc = t.allRefs()) {
+ for (Ref exp : refs) {
+ assertTrue("has " + exp.getName(), rc.next());
+ Ref act = rc.getRef();
+ assertEquals(exp.getName(), act.getName());
+ assertEquals(exp.getObjectId(), act.getObjectId());
+ }
+ assertFalse(rc.next());
+ }
+ }
+
+ private static void assertSeek(List<Ref> refs, Reftable t)
+ throws IOException {
+ for (Ref exp : refs) {
+ try (RefCursor rc = t.seekRef(exp.getName())) {
+ assertTrue("has " + exp.getName(), rc.next());
+ Ref act = rc.getRef();
+ assertEquals(exp.getName(), act.getName());
+ assertEquals(exp.getObjectId(), act.getObjectId());
+ assertFalse(rc.next());
+ }
+ }
+ }
+
+ private static Ref ref(String name, int id) {
+ return new ObjectIdRef.PeeledNonTag(PACKED, name, id(id));
+ }
+
+ private static Ref tag(String name, int id1, int id2) {
+ return new ObjectIdRef.PeeledTag(PACKED, name, id(id1), id(id2));
+ }
+
+ private static Ref sym(String name, String target) {
+ return new SymbolicRef(name, newRef(target));
+ }
+
+ private static Ref newRef(String name) {
+ return new ObjectIdRef.Unpeeled(NEW, name, null);
+ }
+
+ private static ObjectId id(int i) {
+ byte[] buf = new byte[OBJECT_ID_LENGTH];
+ buf[0] = (byte) (i & 0xff);
+ buf[1] = (byte) ((i >>> 8) & 0xff);
+ buf[2] = (byte) ((i >>> 16) & 0xff);
+ buf[3] = (byte) (i >>> 24);
+ return ObjectId.fromRaw(buf);
+ }
+
+ private static ReftableReader read(byte[] table) {
+ return new ReftableReader(BlockSource.from(table));
+ }
+
+ private byte[] write(Ref... refs) throws IOException {
+ return write(Arrays.asList(refs));
+ }
+
+ private byte[] write(Collection<Ref> refs) throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ stats = new ReftableWriter()
+ .begin(buffer)
+ .sortAndWriteRefs(refs)
+ .finish()
+ .getStats();
+ return buffer.toByteArray();
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java
index 67a7819..d5a07e0 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/LocalDiskRefTreeDatabaseTest.java
@@ -83,7 +83,7 @@ public void setUp() throws Exception {
FileRepository init = createWorkRepository();
FileBasedConfig cfg = init.getConfig();
cfg.setInt("core", null, "repositoryformatversion", 1);
- cfg.setString("extensions", null, "refsStorage", "reftree");
+ cfg.setString("extensions", null, "refStorage", "reftree");
cfg.save();
repo = (FileRepository) new FileRepositoryBuilder()
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java
index 6529d9e..30a9626 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java
@@ -86,7 +86,6 @@ public void testFull_FromByteArray() {
final ObjectId f = i.toObjectId();
assertNotNull(f);
assertEquals(ObjectId.fromString(s), f);
- assertEquals(f.hashCode(), i.hashCode());
}
@Test
@@ -101,7 +100,6 @@ public void testFull_FromString() {
final ObjectId f = i.toObjectId();
assertNotNull(f);
assertEquals(ObjectId.fromString(s), f);
- assertEquals(f.hashCode(), i.hashCode());
}
@Test
@@ -215,7 +213,7 @@ public void test17_FromString() {
}
@Test
- public void testEquals_Short() {
+ public void testEquals_Short8() {
final String s = "7b6e8067";
final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(s);
final AbbreviatedObjectId b = AbbreviatedObjectId.fromString(s);
@@ -226,6 +224,18 @@ public void testEquals_Short() {
}
@Test
+ public void testEquals_Short4() {
+ final String s = "7b6e";
+ final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(s);
+ final AbbreviatedObjectId b = AbbreviatedObjectId.fromString(s);
+ assertNotSame(a, b);
+ assertTrue(a.hashCode() != 0);
+ assertTrue(a.hashCode() == b.hashCode());
+ assertEquals(b, a);
+ assertEquals(a, b);
+ }
+
+ @Test
public void testEquals_Full() {
final String s = "7b6e8067ec96acef9a4184b43210d583b6d2f99a";
final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(s);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
index e9505f6..a12831a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
@@ -80,6 +80,7 @@
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.SystemReader;
import org.junit.After;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -766,6 +767,7 @@ public void testReadMultipleValuesForName() throws ConfigInvalidException {
}
@Test
+ @Ignore
public void testIncludeInvalidName() throws ConfigInvalidException {
expectedEx.expect(ConfigInvalidException.class);
expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile);
@@ -773,6 +775,7 @@ public void testIncludeInvalidName() throws ConfigInvalidException {
}
@Test
+ @Ignore
public void testIncludeNoValue() throws ConfigInvalidException {
expectedEx.expect(ConfigInvalidException.class);
expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile);
@@ -780,6 +783,7 @@ public void testIncludeNoValue() throws ConfigInvalidException {
}
@Test
+ @Ignore
public void testIncludeEmptyValue() throws ConfigInvalidException {
expectedEx.expect(ConfigInvalidException.class);
expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile);
@@ -816,6 +820,7 @@ public void testIncludeValuePathRelative() throws ConfigInvalidException {
}
@Test
+ @Ignore
public void testIncludeTooManyRecursions() throws IOException {
File config = tmp.newFile("config");
String include = "[include]\npath=" + config.toPath() + "\n";
@@ -832,27 +837,14 @@ public void testIncludeTooManyRecursions() throws IOException {
}
@Test
- public void testInclude() throws IOException, ConfigInvalidException {
+ public void testIncludeIsNoop() throws IOException, ConfigInvalidException {
File config = tmp.newFile("config");
- File more = tmp.newFile("config.more");
- File other = tmp.newFile("config.other");
String fooBar = "[foo]\nbar=true\n";
- String includeMore = "[include]\npath=" + more.toPath() + "\n";
- String includeOther = "path=" + other.toPath() + "\n";
- String fooPlus = fooBar + includeMore + includeOther;
- Files.write(config.toPath(), fooPlus.getBytes());
-
- String fooMore = "[foo]\nmore=bar\n";
- Files.write(more.toPath(), fooMore.getBytes());
-
- String otherMore = "[other]\nmore=bar\n";
- Files.write(other.toPath(), otherMore.getBytes());
+ Files.write(config.toPath(), fooBar.getBytes());
Config parsed = parse("[include]\npath=" + config.toPath() + "\n");
- assertTrue(parsed.getBoolean("foo", "bar", false));
- assertEquals("bar", parsed.getString("foo", null, "more"));
- assertEquals("bar", parsed.getString("other", null, "more"));
+ assertFalse(parsed.getBoolean("foo", "bar", false));
}
private static void assertReadLong(long exp) throws ConfigInvalidException {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
index f8c2d45..05573b9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
@@ -72,6 +72,8 @@
import org.eclipse.jgit.errors.CheckoutConflictException;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.NoWorkTreeException;
+import org.eclipse.jgit.events.ChangeRecorder;
+import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
@@ -141,14 +143,19 @@ private static HashMap<String, String> mkmap(String... args) {
@Test
public void testResetHard() throws IOException, NoFilepatternException,
GitAPIException {
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
writeTrashFile("f", "f()");
writeTrashFile("D/g", "g()");
git.add().addFilepattern(".").call();
git.commit().setMessage("inital").call();
assertIndex(mkmap("f", "f()", "D/g", "g()"));
-
+ recorder.assertNoEvent();
git.branchCreate().setName("topic").call();
+ recorder.assertNoEvent();
writeTrashFile("f", "f()\nmaster");
writeTrashFile("D/g", "g()\ng2()");
@@ -156,9 +163,12 @@ public void testResetHard() throws IOException, NoFilepatternException,
git.add().addFilepattern(".").call();
RevCommit master = git.commit().setMessage("master-1").call();
assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
+ recorder.assertNoEvent();
checkoutBranch("refs/heads/topic");
assertIndex(mkmap("f", "f()", "D/g", "g()"));
+ recorder.assertEvent(new String[] { "f", "D/g" },
+ new String[] { "E/h" });
writeTrashFile("f", "f()\nside");
assertTrue(new File(db.getWorkTree(), "D/g").delete());
@@ -167,26 +177,41 @@ public void testResetHard() throws IOException, NoFilepatternException,
git.add().addFilepattern(".").setUpdate(true).call();
RevCommit topic = git.commit().setMessage("topic-1").call();
assertIndex(mkmap("f", "f()\nside", "G/i", "i()"));
+ recorder.assertNoEvent();
writeTrashFile("untracked", "untracked");
resetHard(master);
assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
+ recorder.assertEvent(new String[] { "f", "D/g", "E/h" },
+ new String[] { "G", "G/i" });
+
resetHard(topic);
assertIndex(mkmap("f", "f()\nside", "G/i", "i()"));
assertWorkDir(mkmap("f", "f()\nside", "G/i", "i()", "untracked",
"untracked"));
+ recorder.assertEvent(new String[] { "f", "G/i" },
+ new String[] { "D", "D/g", "E", "E/h" });
assertEquals(MergeStatus.CONFLICTING, git.merge().include(master)
.call().getMergeStatus());
assertEquals(
"[D/g, mode:100644, stage:1][D/g, mode:100644, stage:3][E/h, mode:100644][G/i, mode:100644][f, mode:100644, stage:1][f, mode:100644, stage:2][f, mode:100644, stage:3]",
indexState(0));
+ recorder.assertEvent(new String[] { "f", "D/g", "E/h" },
+ ChangeRecorder.EMPTY);
resetHard(master);
assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
assertWorkDir(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h",
"h()", "untracked", "untracked"));
+ recorder.assertEvent(new String[] { "f", "D/g" },
+ new String[] { "G", "G/i" });
+
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
}
}
@@ -202,13 +227,18 @@ public void testResetHard() throws IOException, NoFilepatternException,
@Test
public void testResetHardFromIndexEntryWithoutFileToTreeWithoutFile()
throws Exception {
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
writeTrashFile("x", "x");
git.add().addFilepattern("x").call();
RevCommit id1 = git.commit().setMessage("c1").call();
writeTrashFile("f/g", "f/g");
git.rm().addFilepattern("x").call();
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "x" });
git.add().addFilepattern("f/g").call();
git.commit().setMessage("c2").call();
deleteTrashFile("f/g");
@@ -217,6 +247,11 @@ public void testResetHardFromIndexEntryWithoutFileToTreeWithoutFile()
// The actual test
git.reset().setMode(ResetType.HARD).setRef(id1.getName()).call();
assertIndex(mkmap("x", "x"));
+ recorder.assertEvent(new String[] { "x" }, ChangeRecorder.EMPTY);
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
}
}
@@ -227,13 +262,22 @@ public void testResetHardFromIndexEntryWithoutFileToTreeWithoutFile()
*/
@Test
public void testInitialCheckout() throws Exception {
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
TestRepository<Repository> db_t = new TestRepository<>(db);
BranchBuilder master = db_t.branch("master");
master.commit().add("f", "1").message("m0").create();
assertFalse(new File(db.getWorkTree(), "f").exists());
git.checkout().setName("master").call();
assertTrue(new File(db.getWorkTree(), "f").exists());
+ recorder.assertEvent(new String[] { "f" }, ChangeRecorder.EMPTY);
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
}
}
@@ -930,120 +974,154 @@ public void testCheckoutOutChanges() throws IOException {
public void testCheckoutChangeLinkToEmptyDir() throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
String fname = "was_file";
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
- // Add a file
- writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
+ // Add a link to file
+ String linkName = "link";
+ File link = writeLink(linkName, fname).toFile();
+ git.add().addFilepattern(linkName).call();
+ git.commit().setMessage("Added file and link").call();
- // Add a link to file
- String linkName = "link";
- File link = writeLink(linkName, fname).toFile();
- git.add().addFilepattern(linkName).call();
- git.commit().setMessage("Added file and link").call();
+ assertWorkDir(mkmap(linkName, "a", fname, "a"));
- assertWorkDir(mkmap(linkName, "a", fname, "a"));
+ // replace link with empty directory
+ FileUtils.delete(link);
+ FileUtils.mkdir(link);
+ assertTrue("Link must be a directory now", link.isDirectory());
- // replace link with empty directory
- FileUtils.delete(link);
- FileUtils.mkdir(link);
- assertTrue("Link must be a directory now", link.isDirectory());
+ // modify file
+ writeTrashFile(fname, "b");
+ assertWorkDir(mkmap(fname, "b", linkName, "/"));
+ recorder.assertNoEvent();
- // modify file
- writeTrashFile(fname, "b");
- assertWorkDir(mkmap(fname, "b", linkName, "/"));
+ // revert both paths to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(fname)
+ .addPath(linkName).call();
- // revert both paths to HEAD state
- git.checkout().setStartPoint(Constants.HEAD)
- .addPath(fname).addPath(linkName).call();
+ assertWorkDir(mkmap(fname, "a", linkName, "a"));
+ recorder.assertEvent(new String[] { fname, linkName },
+ ChangeRecorder.EMPTY);
- assertWorkDir(mkmap(fname, "a", linkName, "a"));
-
- Status st = git.status().call();
- assertTrue(st.isClean());
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
public void testCheckoutChangeLinkToEmptyDirs() throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
String fname = "was_file";
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
- // Add a file
- writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
+ // Add a link to file
+ String linkName = "link";
+ File link = writeLink(linkName, fname).toFile();
+ git.add().addFilepattern(linkName).call();
+ git.commit().setMessage("Added file and link").call();
- // Add a link to file
- String linkName = "link";
- File link = writeLink(linkName, fname).toFile();
- git.add().addFilepattern(linkName).call();
- git.commit().setMessage("Added file and link").call();
+ assertWorkDir(mkmap(linkName, "a", fname, "a"));
- assertWorkDir(mkmap(linkName, "a", fname, "a"));
+ // replace link with directory containing only directories, no files
+ FileUtils.delete(link);
+ FileUtils.mkdirs(new File(link, "dummyDir"));
+ assertTrue("Link must be a directory now", link.isDirectory());
- // replace link with directory containing only directories, no files
- FileUtils.delete(link);
- FileUtils.mkdirs(new File(link, "dummyDir"));
- assertTrue("Link must be a directory now", link.isDirectory());
+ assertFalse("Must not delete non empty directory", link.delete());
- assertFalse("Must not delete non empty directory", link.delete());
+ // modify file
+ writeTrashFile(fname, "b");
+ assertWorkDir(mkmap(fname, "b", linkName + "/dummyDir", "/"));
+ recorder.assertNoEvent();
- // modify file
- writeTrashFile(fname, "b");
- assertWorkDir(mkmap(fname, "b", linkName + "/dummyDir", "/"));
+ // revert both paths to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(fname)
+ .addPath(linkName).call();
- // revert both paths to HEAD state
- git.checkout().setStartPoint(Constants.HEAD)
- .addPath(fname).addPath(linkName).call();
+ assertWorkDir(mkmap(fname, "a", linkName, "a"));
+ recorder.assertEvent(new String[] { fname, linkName },
+ ChangeRecorder.EMPTY);
- assertWorkDir(mkmap(fname, "a", linkName, "a"));
-
- Status st = git.status().call();
- assertTrue(st.isClean());
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
public void testCheckoutChangeLinkToNonEmptyDirs() throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
String fname = "file";
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
- // Add a file
- writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
+ // Add a link to file
+ String linkName = "link";
+ File link = writeLink(linkName, fname).toFile();
+ git.add().addFilepattern(linkName).call();
+ git.commit().setMessage("Added file and link").call();
- // Add a link to file
- String linkName = "link";
- File link = writeLink(linkName, fname).toFile();
- git.add().addFilepattern(linkName).call();
- git.commit().setMessage("Added file and link").call();
+ assertWorkDir(mkmap(linkName, "a", fname, "a"));
- assertWorkDir(mkmap(linkName, "a", fname, "a"));
+ // replace link with directory containing only directories, no files
+ FileUtils.delete(link);
- // replace link with directory containing only directories, no files
- FileUtils.delete(link);
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(linkName + "/dir1", "file1", "c");
- // create but do not add a file in the new directory to the index
- writeTrashFile(linkName + "/dir1", "file1", "c");
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(linkName + "/dir2", "file2", "d");
- // create but do not add a file in the new directory to the index
- writeTrashFile(linkName + "/dir2", "file2", "d");
+ assertTrue("File must be a directory now", link.isDirectory());
+ assertFalse("Must not delete non empty directory", link.delete());
- assertTrue("File must be a directory now", link.isDirectory());
- assertFalse("Must not delete non empty directory", link.delete());
+ // 2 extra files are created
+ assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
+ linkName + "/dir2/file2", "d"));
+ recorder.assertNoEvent();
- // 2 extra files are created
- assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
- linkName + "/dir2/file2", "d"));
+ // revert path to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(linkName)
+ .call();
- // revert path to HEAD state
- git.checkout().setStartPoint(Constants.HEAD).addPath(linkName).call();
+ // expect only the one added to the index
+ assertWorkDir(mkmap(linkName, "a", fname, "a"));
+ recorder.assertEvent(new String[] { linkName },
+ ChangeRecorder.EMPTY);
- // expect only the one added to the index
- assertWorkDir(mkmap(linkName, "a", fname, "a"));
-
- Status st = git.status().call();
- assertTrue(st.isClean());
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
@@ -1051,174 +1129,222 @@ public void testCheckoutChangeLinkToNonEmptyDirsAndNewIndexEntry()
throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
String fname = "file";
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
- // Add a file
- writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
+ // Add a link to file
+ String linkName = "link";
+ File link = writeLink(linkName, fname).toFile();
+ git.add().addFilepattern(linkName).call();
+ git.commit().setMessage("Added file and link").call();
- // Add a link to file
- String linkName = "link";
- File link = writeLink(linkName, fname).toFile();
- git.add().addFilepattern(linkName).call();
- git.commit().setMessage("Added file and link").call();
+ assertWorkDir(mkmap(linkName, "a", fname, "a"));
- assertWorkDir(mkmap(linkName, "a", fname, "a"));
+ // replace link with directory containing only directories, no files
+ FileUtils.delete(link);
- // replace link with directory containing only directories, no files
- FileUtils.delete(link);
+ // create and add a file in the new directory to the index
+ writeTrashFile(linkName + "/dir1", "file1", "c");
+ git.add().addFilepattern(linkName + "/dir1/file1").call();
- // create and add a file in the new directory to the index
- writeTrashFile(linkName + "/dir1", "file1", "c");
- git.add().addFilepattern(linkName + "/dir1/file1").call();
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(linkName + "/dir2", "file2", "d");
- // create but do not add a file in the new directory to the index
- writeTrashFile(linkName + "/dir2", "file2", "d");
+ assertTrue("File must be a directory now", link.isDirectory());
+ assertFalse("Must not delete non empty directory", link.delete());
- assertTrue("File must be a directory now", link.isDirectory());
- assertFalse("Must not delete non empty directory", link.delete());
+ // 2 extra files are created
+ assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
+ linkName + "/dir2/file2", "d"));
+ recorder.assertNoEvent();
- // 2 extra files are created
- assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
- linkName + "/dir2/file2", "d"));
+ // revert path to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(linkName)
+ .call();
- // revert path to HEAD state
- git.checkout().setStartPoint(Constants.HEAD).addPath(linkName).call();
+ // original file and link
+ assertWorkDir(mkmap(linkName, "a", fname, "a"));
+ recorder.assertEvent(new String[] { linkName },
+ ChangeRecorder.EMPTY);
- // original file and link
- assertWorkDir(mkmap(linkName, "a", fname, "a"));
-
- Status st = git.status().call();
- assertTrue(st.isClean());
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
public void testCheckoutChangeFileToEmptyDir() throws Exception {
String fname = "was_file";
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ File file = writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
+ git.commit().setMessage("Added file").call();
- // Add a file
- File file = writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
- git.commit().setMessage("Added file").call();
+ // replace file with empty directory
+ FileUtils.delete(file);
+ FileUtils.mkdir(file);
+ assertTrue("File must be a directory now", file.isDirectory());
+ assertWorkDir(mkmap(fname, "/"));
+ recorder.assertNoEvent();
- // replace file with empty directory
- FileUtils.delete(file);
- FileUtils.mkdir(file);
- assertTrue("File must be a directory now", file.isDirectory());
+ // revert path to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
+ assertWorkDir(mkmap(fname, "a"));
+ recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
- assertWorkDir(mkmap(fname, "/"));
-
- // revert path to HEAD state
- git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
-
- assertWorkDir(mkmap(fname, "a"));
-
- Status st = git.status().call();
- assertTrue(st.isClean());
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
public void testCheckoutChangeFileToEmptyDirs() throws Exception {
String fname = "was_file";
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ File file = writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
+ git.commit().setMessage("Added file").call();
- // Add a file
- File file = writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
- git.commit().setMessage("Added file").call();
+ // replace file with directory containing only directories, no files
+ FileUtils.delete(file);
+ FileUtils.mkdirs(new File(file, "dummyDir"));
+ assertTrue("File must be a directory now", file.isDirectory());
+ assertFalse("Must not delete non empty directory", file.delete());
- // replace file with directory containing only directories, no files
- FileUtils.delete(file);
- FileUtils.mkdirs(new File(file, "dummyDir"));
- assertTrue("File must be a directory now", file.isDirectory());
- assertFalse("Must not delete non empty directory", file.delete());
+ assertWorkDir(mkmap(fname + "/dummyDir", "/"));
+ recorder.assertNoEvent();
- assertWorkDir(mkmap(fname + "/dummyDir", "/"));
+ // revert path to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
+ assertWorkDir(mkmap(fname, "a"));
+ recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
- // revert path to HEAD state
- git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
-
- assertWorkDir(mkmap(fname, "a"));
-
- Status st = git.status().call();
- assertTrue(st.isClean());
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
public void testCheckoutChangeFileToNonEmptyDirs() throws Exception {
String fname = "was_file";
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ File file = writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
+ git.commit().setMessage("Added file").call();
- // Add a file
- File file = writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
- git.commit().setMessage("Added file").call();
+ assertWorkDir(mkmap(fname, "a"));
- assertWorkDir(mkmap(fname, "a"));
+ // replace file with directory containing only directories, no files
+ FileUtils.delete(file);
- // replace file with directory containing only directories, no files
- FileUtils.delete(file);
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(fname + "/dir1", "file1", "c");
- // create but do not add a file in the new directory to the index
- writeTrashFile(fname + "/dir1", "file1", "c");
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(fname + "/dir2", "file2", "d");
- // create but do not add a file in the new directory to the index
- writeTrashFile(fname + "/dir2", "file2", "d");
+ assertTrue("File must be a directory now", file.isDirectory());
+ assertFalse("Must not delete non empty directory", file.delete());
- assertTrue("File must be a directory now", file.isDirectory());
- assertFalse("Must not delete non empty directory", file.delete());
+ // 2 extra files are created
+ assertWorkDir(mkmap(fname + "/dir1/file1", "c",
+ fname + "/dir2/file2", "d"));
+ recorder.assertNoEvent();
- // 2 extra files are created
- assertWorkDir(
- mkmap(fname + "/dir1/file1", "c", fname + "/dir2/file2", "d"));
+ // revert path to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
- // revert path to HEAD state
- git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
+ // expect only the one added to the index
+ assertWorkDir(mkmap(fname, "a"));
+ recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
- // expect only the one added to the index
- assertWorkDir(mkmap(fname, "a"));
-
- Status st = git.status().call();
- assertTrue(st.isClean());
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
public void testCheckoutChangeFileToNonEmptyDirsAndNewIndexEntry()
throws Exception {
String fname = "was_file";
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ File file = writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
+ git.commit().setMessage("Added file").call();
- // Add a file
- File file = writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
- git.commit().setMessage("Added file").call();
+ assertWorkDir(mkmap(fname, "a"));
- assertWorkDir(mkmap(fname, "a"));
+ // replace file with directory containing only directories, no files
+ FileUtils.delete(file);
- // replace file with directory containing only directories, no files
- FileUtils.delete(file);
+ // create and add a file in the new directory to the index
+ writeTrashFile(fname + "/dir", "file1", "c");
+ git.add().addFilepattern(fname + "/dir/file1").call();
- // create and add a file in the new directory to the index
- writeTrashFile(fname + "/dir", "file1", "c");
- git.add().addFilepattern(fname + "/dir/file1").call();
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(fname + "/dir", "file2", "d");
- // create but do not add a file in the new directory to the index
- writeTrashFile(fname + "/dir", "file2", "d");
+ assertTrue("File must be a directory now", file.isDirectory());
+ assertFalse("Must not delete non empty directory", file.delete());
- assertTrue("File must be a directory now", file.isDirectory());
- assertFalse("Must not delete non empty directory", file.delete());
+ // 2 extra files are created
+ assertWorkDir(mkmap(fname + "/dir/file1", "c", fname + "/dir/file2",
+ "d"));
+ recorder.assertNoEvent();
- // 2 extra files are created
- assertWorkDir(
- mkmap(fname + "/dir/file1", "c", fname + "/dir/file2", "d"));
-
- // revert path to HEAD state
- git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
- assertWorkDir(mkmap(fname, "a"));
-
- Status st = git.status().call();
- assertTrue(st.isClean());
+ // revert path to HEAD state
+ git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
+ assertWorkDir(mkmap(fname, "a"));
+ recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
+ Status st = git.status().call();
+ assertTrue(st.isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
@@ -1293,76 +1419,100 @@ public void testDontOverwriteEmptyFolder() throws IOException {
public void testOverwriteUntrackedIgnoredFile() throws IOException,
GitAPIException {
String fname="file.txt";
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
+ git.commit().setMessage("create file").call();
- // Add a file
- writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
- git.commit().setMessage("create file").call();
+ // Create branch
+ git.branchCreate().setName("side").call();
- // Create branch
- git.branchCreate().setName("side").call();
+ // Modify file
+ writeTrashFile(fname, "b");
+ git.add().addFilepattern(fname).call();
+ git.commit().setMessage("modify file").call();
+ recorder.assertNoEvent();
- // Modify file
- writeTrashFile(fname, "b");
- git.add().addFilepattern(fname).call();
- git.commit().setMessage("modify file").call();
+ // Switch branches
+ git.checkout().setName("side").call();
+ recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
+ git.rm().addFilepattern(fname).call();
+ recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { fname });
+ writeTrashFile(".gitignore", fname);
+ git.add().addFilepattern(".gitignore").call();
+ git.commit().setMessage("delete and ignore file").call();
- // Switch branches
- git.checkout().setName("side").call();
- git.rm().addFilepattern(fname).call();
- writeTrashFile(".gitignore", fname);
- git.add().addFilepattern(".gitignore").call();
- git.commit().setMessage("delete and ignore file").call();
-
- writeTrashFile(fname, "Something different");
- git.checkout().setName("master").call();
- assertWorkDir(mkmap(fname, "b"));
- assertTrue(git.status().call().isClean());
+ writeTrashFile(fname, "Something different");
+ recorder.assertNoEvent();
+ git.checkout().setName("master").call();
+ assertWorkDir(mkmap(fname, "b"));
+ recorder.assertEvent(new String[] { fname },
+ new String[] { ".gitignore" });
+ assertTrue(git.status().call().isClean());
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
public void testOverwriteUntrackedFileModeChange()
throws IOException, GitAPIException {
String fname = "file.txt";
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ File file = writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
+ git.commit().setMessage("create file").call();
+ assertWorkDir(mkmap(fname, "a"));
- // Add a file
- File file = writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
- git.commit().setMessage("create file").call();
- assertWorkDir(mkmap(fname, "a"));
+ // Create branch
+ git.branchCreate().setName("side").call();
- // Create branch
- git.branchCreate().setName("side").call();
+ // Switch branches
+ git.checkout().setName("side").call();
+ recorder.assertNoEvent();
- // Switch branches
- git.checkout().setName("side").call();
+ // replace file with directory containing files
+ FileUtils.delete(file);
- // replace file with directory containing files
- FileUtils.delete(file);
+ // create and add a file in the new directory to the index
+ writeTrashFile(fname + "/dir1", "file1", "c");
+ git.add().addFilepattern(fname + "/dir1/file1").call();
- // create and add a file in the new directory to the index
- writeTrashFile(fname + "/dir1", "file1", "c");
- git.add().addFilepattern(fname + "/dir1/file1").call();
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(fname + "/dir2", "file2", "d");
- // create but do not add a file in the new directory to the index
- writeTrashFile(fname + "/dir2", "file2", "d");
+ assertTrue("File must be a directory now", file.isDirectory());
+ assertFalse("Must not delete non empty directory", file.delete());
- assertTrue("File must be a directory now", file.isDirectory());
- assertFalse("Must not delete non empty directory", file.delete());
-
- // 2 extra files are created
- assertWorkDir(
- mkmap(fname + "/dir1/file1", "c", fname + "/dir2/file2", "d"));
-
- try {
- git.checkout().setName("master").call();
- fail("did not throw exception");
- } catch (Exception e) {
- // 2 extra files are still there
+ // 2 extra files are created
assertWorkDir(mkmap(fname + "/dir1/file1", "c",
fname + "/dir2/file2", "d"));
+
+ try {
+ git.checkout().setName("master").call();
+ fail("did not throw exception");
+ } catch (Exception e) {
+ // 2 extra files are still there
+ assertWorkDir(mkmap(fname + "/dir1/file1", "c",
+ fname + "/dir2/file2", "d"));
+ }
+ recorder.assertNoEvent();
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
}
}
@@ -1371,50 +1521,60 @@ public void testOverwriteUntrackedLinkModeChange()
throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
String fname = "file.txt";
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add a file
+ writeTrashFile(fname, "a");
+ git.add().addFilepattern(fname).call();
- // Add a file
- writeTrashFile(fname, "a");
- git.add().addFilepattern(fname).call();
+ // Add a link to file
+ String linkName = "link";
+ File link = writeLink(linkName, fname).toFile();
+ git.add().addFilepattern(linkName).call();
+ git.commit().setMessage("Added file and link").call();
- // Add a link to file
- String linkName = "link";
- File link = writeLink(linkName, fname).toFile();
- git.add().addFilepattern(linkName).call();
- git.commit().setMessage("Added file and link").call();
+ assertWorkDir(mkmap(linkName, "a", fname, "a"));
- assertWorkDir(mkmap(linkName, "a", fname, "a"));
+ // Create branch
+ git.branchCreate().setName("side").call();
- // Create branch
- git.branchCreate().setName("side").call();
+ // Switch branches
+ git.checkout().setName("side").call();
+ recorder.assertNoEvent();
- // Switch branches
- git.checkout().setName("side").call();
+ // replace link with directory containing files
+ FileUtils.delete(link);
- // replace link with directory containing files
- FileUtils.delete(link);
+ // create and add a file in the new directory to the index
+ writeTrashFile(linkName + "/dir1", "file1", "c");
+ git.add().addFilepattern(linkName + "/dir1/file1").call();
- // create and add a file in the new directory to the index
- writeTrashFile(linkName + "/dir1", "file1", "c");
- git.add().addFilepattern(linkName + "/dir1/file1").call();
+ // create but do not add a file in the new directory to the index
+ writeTrashFile(linkName + "/dir2", "file2", "d");
- // create but do not add a file in the new directory to the index
- writeTrashFile(linkName + "/dir2", "file2", "d");
+ assertTrue("Link must be a directory now", link.isDirectory());
+ assertFalse("Must not delete non empty directory", link.delete());
- assertTrue("Link must be a directory now", link.isDirectory());
- assertFalse("Must not delete non empty directory", link.delete());
-
- // 2 extra files are created
- assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
- linkName + "/dir2/file2", "d"));
-
- try {
- git.checkout().setName("master").call();
- fail("did not throw exception");
- } catch (Exception e) {
- // 2 extra files are still there
+ // 2 extra files are created
assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
linkName + "/dir2/file2", "d"));
+
+ try {
+ git.checkout().setName("master").call();
+ fail("did not throw exception");
+ } catch (Exception e) {
+ // 2 extra files are still there
+ assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
+ linkName + "/dir2/file2", "d"));
+ }
+ recorder.assertNoEvent();
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
}
}
@@ -1423,36 +1583,47 @@ public void testFileModeChangeWithNoContentChangeUpdate() throws Exception {
if (!FS.DETECTED.supportsExecute())
return;
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add non-executable file
+ File file = writeTrashFile("file.txt", "a");
+ git.add().addFilepattern("file.txt").call();
+ git.commit().setMessage("commit1").call();
+ assertFalse(db.getFS().canExecute(file));
- // Add non-executable file
- File file = writeTrashFile("file.txt", "a");
- git.add().addFilepattern("file.txt").call();
- git.commit().setMessage("commit1").call();
- assertFalse(db.getFS().canExecute(file));
+ // Create branch
+ git.branchCreate().setName("b1").call();
- // Create branch
- git.branchCreate().setName("b1").call();
+ // Make file executable
+ db.getFS().setExecute(file, true);
+ git.add().addFilepattern("file.txt").call();
+ git.commit().setMessage("commit2").call();
+ recorder.assertNoEvent();
- // Make file executable
- db.getFS().setExecute(file, true);
- git.add().addFilepattern("file.txt").call();
- git.commit().setMessage("commit2").call();
+ // Verify executable and working directory is clean
+ Status status = git.status().call();
+ assertTrue(status.getModified().isEmpty());
+ assertTrue(status.getChanged().isEmpty());
+ assertTrue(db.getFS().canExecute(file));
- // Verify executable and working directory is clean
- Status status = git.status().call();
- assertTrue(status.getModified().isEmpty());
- assertTrue(status.getChanged().isEmpty());
- assertTrue(db.getFS().canExecute(file));
+ // Switch branches
+ git.checkout().setName("b1").call();
- // Switch branches
- git.checkout().setName("b1").call();
-
- // Verify not executable and working directory is clean
- status = git.status().call();
- assertTrue(status.getModified().isEmpty());
- assertTrue(status.getChanged().isEmpty());
- assertFalse(db.getFS().canExecute(file));
+ // Verify not executable and working directory is clean
+ status = git.status().call();
+ assertTrue(status.getModified().isEmpty());
+ assertTrue(status.getChanged().isEmpty());
+ assertFalse(db.getFS().canExecute(file));
+ recorder.assertEvent(new String[] { "file.txt" },
+ ChangeRecorder.EMPTY);
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
@@ -1460,41 +1631,50 @@ public void testFileModeChangeAndContentChangeConflict() throws Exception {
if (!FS.DETECTED.supportsExecute())
return;
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add non-executable file
+ File file = writeTrashFile("file.txt", "a");
+ git.add().addFilepattern("file.txt").call();
+ git.commit().setMessage("commit1").call();
+ assertFalse(db.getFS().canExecute(file));
- // Add non-executable file
- File file = writeTrashFile("file.txt", "a");
- git.add().addFilepattern("file.txt").call();
- git.commit().setMessage("commit1").call();
- assertFalse(db.getFS().canExecute(file));
+ // Create branch
+ git.branchCreate().setName("b1").call();
- // Create branch
- git.branchCreate().setName("b1").call();
+ // Make file executable
+ db.getFS().setExecute(file, true);
+ git.add().addFilepattern("file.txt").call();
+ git.commit().setMessage("commit2").call();
- // Make file executable
- db.getFS().setExecute(file, true);
- git.add().addFilepattern("file.txt").call();
- git.commit().setMessage("commit2").call();
+ // Verify executable and working directory is clean
+ Status status = git.status().call();
+ assertTrue(status.getModified().isEmpty());
+ assertTrue(status.getChanged().isEmpty());
+ assertTrue(db.getFS().canExecute(file));
- // Verify executable and working directory is clean
- Status status = git.status().call();
- assertTrue(status.getModified().isEmpty());
- assertTrue(status.getChanged().isEmpty());
- assertTrue(db.getFS().canExecute(file));
+ writeTrashFile("file.txt", "b");
- writeTrashFile("file.txt", "b");
-
- // Switch branches
- CheckoutCommand checkout = git.checkout().setName("b1");
- try {
- checkout.call();
- fail("Checkout exception not thrown");
- } catch (org.eclipse.jgit.api.errors.CheckoutConflictException e) {
- CheckoutResult result = checkout.getResult();
- assertNotNull(result);
- assertNotNull(result.getConflictList());
- assertEquals(1, result.getConflictList().size());
- assertTrue(result.getConflictList().contains("file.txt"));
+ // Switch branches
+ CheckoutCommand checkout = git.checkout().setName("b1");
+ try {
+ checkout.call();
+ fail("Checkout exception not thrown");
+ } catch (org.eclipse.jgit.api.errors.CheckoutConflictException e) {
+ CheckoutResult result = checkout.getResult();
+ assertNotNull(result);
+ assertNotNull(result.getConflictList());
+ assertEquals(1, result.getConflictList().size());
+ assertTrue(result.getConflictList().contains("file.txt"));
+ }
+ recorder.assertNoEvent();
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
}
}
@@ -1504,40 +1684,52 @@ public void testDirtyFileModeEqualHeadMerge()
if (!FS.DETECTED.supportsExecute())
return;
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add non-executable file
+ File file = writeTrashFile("file.txt", "a");
+ git.add().addFilepattern("file.txt").call();
+ git.commit().setMessage("commit1").call();
+ assertFalse(db.getFS().canExecute(file));
- // Add non-executable file
- File file = writeTrashFile("file.txt", "a");
- git.add().addFilepattern("file.txt").call();
- git.commit().setMessage("commit1").call();
- assertFalse(db.getFS().canExecute(file));
+ // Create branch
+ git.branchCreate().setName("b1").call();
- // Create branch
- git.branchCreate().setName("b1").call();
+ // Create second commit and don't touch file
+ writeTrashFile("file2.txt", "");
+ git.add().addFilepattern("file2.txt").call();
+ git.commit().setMessage("commit2").call();
- // Create second commit and don't touch file
- writeTrashFile("file2.txt", "");
- git.add().addFilepattern("file2.txt").call();
- git.commit().setMessage("commit2").call();
+ // stage a mode change
+ writeTrashFile("file.txt", "a");
+ db.getFS().setExecute(file, true);
+ git.add().addFilepattern("file.txt").call();
- // stage a mode change
- writeTrashFile("file.txt", "a");
- db.getFS().setExecute(file, true);
- git.add().addFilepattern("file.txt").call();
+ // dirty the file
+ writeTrashFile("file.txt", "b");
- // dirty the file
- writeTrashFile("file.txt", "b");
+ assertEquals(
+ "[file.txt, mode:100755, content:a][file2.txt, mode:100644, content:]",
+ indexState(CONTENT));
+ assertWorkDir(mkmap("file.txt", "b", "file2.txt", ""));
+ recorder.assertNoEvent();
- assertEquals(
- "[file.txt, mode:100755, content:a][file2.txt, mode:100644, content:]",
- indexState(CONTENT));
- assertWorkDir(mkmap("file.txt", "b", "file2.txt", ""));
-
- // Switch branches and check that the dirty file survived in worktree
- // and index
- git.checkout().setName("b1").call();
- assertEquals("[file.txt, mode:100755, content:a]", indexState(CONTENT));
- assertWorkDir(mkmap("file.txt", "b"));
+ // Switch branches and check that the dirty file survived in
+ // worktree and index
+ git.checkout().setName("b1").call();
+ assertEquals("[file.txt, mode:100755, content:a]",
+ indexState(CONTENT));
+ assertWorkDir(mkmap("file.txt", "b"));
+ recorder.assertEvent(ChangeRecorder.EMPTY,
+ new String[] { "file2.txt" });
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
@@ -1546,40 +1738,53 @@ public void testDirtyFileModeEqualIndexMerge()
if (!FS.DETECTED.supportsExecute())
return;
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add non-executable file
+ File file = writeTrashFile("file.txt", "a");
+ git.add().addFilepattern("file.txt").call();
+ git.commit().setMessage("commit1").call();
+ assertFalse(db.getFS().canExecute(file));
- // Add non-executable file
- File file = writeTrashFile("file.txt", "a");
- git.add().addFilepattern("file.txt").call();
- git.commit().setMessage("commit1").call();
- assertFalse(db.getFS().canExecute(file));
+ // Create branch
+ git.branchCreate().setName("b1").call();
- // Create branch
- git.branchCreate().setName("b1").call();
+ // Create second commit with executable file
+ file = writeTrashFile("file.txt", "b");
+ db.getFS().setExecute(file, true);
+ git.add().addFilepattern("file.txt").call();
+ git.commit().setMessage("commit2").call();
- // Create second commit with executable file
- file = writeTrashFile("file.txt", "b");
- db.getFS().setExecute(file, true);
- git.add().addFilepattern("file.txt").call();
- git.commit().setMessage("commit2").call();
+ // stage the same content as in the branch we want to switch to
+ writeTrashFile("file.txt", "a");
+ db.getFS().setExecute(file, false);
+ git.add().addFilepattern("file.txt").call();
- // stage the same content as in the branch we want to switch to
- writeTrashFile("file.txt", "a");
- db.getFS().setExecute(file, false);
- git.add().addFilepattern("file.txt").call();
+ // dirty the file
+ writeTrashFile("file.txt", "c");
+ db.getFS().setExecute(file, true);
- // dirty the file
- writeTrashFile("file.txt", "c");
- db.getFS().setExecute(file, true);
+ assertEquals("[file.txt, mode:100644, content:a]",
+ indexState(CONTENT));
+ assertWorkDir(mkmap("file.txt", "c"));
+ recorder.assertNoEvent();
- assertEquals("[file.txt, mode:100644, content:a]", indexState(CONTENT));
- assertWorkDir(mkmap("file.txt", "c"));
-
- // Switch branches and check that the dirty file survived in worktree
- // and index
- git.checkout().setName("b1").call();
- assertEquals("[file.txt, mode:100644, content:a]", indexState(CONTENT));
- assertWorkDir(mkmap("file.txt", "c"));
+ // Switch branches and check that the dirty file survived in
+ // worktree
+ // and index
+ git.checkout().setName("b1").call();
+ assertEquals("[file.txt, mode:100644, content:a]",
+ indexState(CONTENT));
+ assertWorkDir(mkmap("file.txt", "c"));
+ recorder.assertNoEvent();
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test
@@ -1587,31 +1792,44 @@ public void testFileModeChangeAndContentChangeNoConflict() throws Exception {
if (!FS.DETECTED.supportsExecute())
return;
- Git git = Git.wrap(db);
+ ChangeRecorder recorder = new ChangeRecorder();
+ ListenerHandle handle = null;
+ try (Git git = new Git(db)) {
+ handle = db.getListenerList()
+ .addWorkingTreeModifiedListener(recorder);
+ // Add first file
+ File file1 = writeTrashFile("file1.txt", "a");
+ git.add().addFilepattern("file1.txt").call();
+ git.commit().setMessage("commit1").call();
+ assertFalse(db.getFS().canExecute(file1));
- // Add first file
- File file1 = writeTrashFile("file1.txt", "a");
- git.add().addFilepattern("file1.txt").call();
- git.commit().setMessage("commit1").call();
- assertFalse(db.getFS().canExecute(file1));
+ // Add second file
+ File file2 = writeTrashFile("file2.txt", "b");
+ git.add().addFilepattern("file2.txt").call();
+ git.commit().setMessage("commit2").call();
+ assertFalse(db.getFS().canExecute(file2));
+ recorder.assertNoEvent();
- // Add second file
- File file2 = writeTrashFile("file2.txt", "b");
- git.add().addFilepattern("file2.txt").call();
- git.commit().setMessage("commit2").call();
- assertFalse(db.getFS().canExecute(file2));
+ // Create branch from first commit
+ assertNotNull(git.checkout().setCreateBranch(true).setName("b1")
+ .setStartPoint(Constants.HEAD + "~1").call());
+ recorder.assertEvent(ChangeRecorder.EMPTY,
+ new String[] { "file2.txt" });
- // Create branch from first commit
- assertNotNull(git.checkout().setCreateBranch(true).setName("b1")
- .setStartPoint(Constants.HEAD + "~1").call());
+ // Change content and file mode in working directory and index
+ file1 = writeTrashFile("file1.txt", "c");
+ db.getFS().setExecute(file1, true);
+ git.add().addFilepattern("file1.txt").call();
- // Change content and file mode in working directory and index
- file1 = writeTrashFile("file1.txt", "c");
- db.getFS().setExecute(file1, true);
- git.add().addFilepattern("file1.txt").call();
-
- // Switch back to 'master'
- assertNotNull(git.checkout().setName(Constants.MASTER).call());
+ // Switch back to 'master'
+ assertNotNull(git.checkout().setName(Constants.MASTER).call());
+ recorder.assertEvent(new String[] { "file2.txt" },
+ ChangeRecorder.EMPTY);
+ } finally {
+ if (handle != null) {
+ handle.remove();
+ }
+ }
}
@Test(expected = CheckoutConflictException.class)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
index 0111b94..d89aabe 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java
@@ -43,11 +43,13 @@
package org.eclipse.jgit.lib;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
+import java.util.Set;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -118,6 +120,31 @@ public void testDirtyRootWorktree(IgnoreSubmoduleMode mode)
assertTrue(indexDiff.diff());
}
+ private void assertDiff(IndexDiff indexDiff, IgnoreSubmoduleMode mode,
+ IgnoreSubmoduleMode... expectedEmptyModes) throws IOException {
+ boolean diffResult = indexDiff.diff();
+ Set<String> submodulePaths = indexDiff
+ .getPathsWithIndexMode(FileMode.GITLINK);
+ boolean emptyExpected = false;
+ for (IgnoreSubmoduleMode empty : expectedEmptyModes) {
+ if (mode.equals(empty)) {
+ emptyExpected = true;
+ break;
+ }
+ }
+ if (emptyExpected) {
+ assertFalse("diff should be false with mode=" + mode,
+ diffResult);
+ assertEquals("should have no paths with FileMode.GITLINK", 0,
+ submodulePaths.size());
+ } else {
+ assertTrue("diff should be true with mode=" + mode,
+ diffResult);
+ assertTrue("submodule path should have FileMode.GITLINK",
+ submodulePaths.contains("modules/submodule"));
+ }
+ }
+
@Theory
public void testDirtySubmoduleWorktree(IgnoreSubmoduleMode mode)
throws IOException {
@@ -125,13 +152,8 @@ public void testDirtySubmoduleWorktree(IgnoreSubmoduleMode mode)
IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
new FileTreeIterator(db));
indexDiff.setIgnoreSubmoduleMode(mode);
- if (mode.equals(IgnoreSubmoduleMode.ALL)
- || mode.equals(IgnoreSubmoduleMode.DIRTY))
- assertFalse("diff should be false with mode=" + mode,
- indexDiff.diff());
- else
- assertTrue("diff should be true with mode=" + mode,
- indexDiff.diff());
+ assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL,
+ IgnoreSubmoduleMode.DIRTY);
}
@Theory
@@ -145,12 +167,7 @@ public void testDirtySubmoduleHEAD(IgnoreSubmoduleMode mode)
IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
new FileTreeIterator(db));
indexDiff.setIgnoreSubmoduleMode(mode);
- if (mode.equals(IgnoreSubmoduleMode.ALL))
- assertFalse("diff should be false with mode=" + mode,
- indexDiff.diff());
- else
- assertTrue("diff should be true with mode=" + mode,
- indexDiff.diff());
+ assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL);
}
@Theory
@@ -163,13 +180,8 @@ public void testDirtySubmoduleIndex(IgnoreSubmoduleMode mode)
IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
new FileTreeIterator(db));
indexDiff.setIgnoreSubmoduleMode(mode);
- if (mode.equals(IgnoreSubmoduleMode.ALL)
- || mode.equals(IgnoreSubmoduleMode.DIRTY))
- assertFalse("diff should be false with mode=" + mode,
- indexDiff.diff());
- else
- assertTrue("diff should be true with mode=" + mode,
- indexDiff.diff());
+ assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL,
+ IgnoreSubmoduleMode.DIRTY);
}
@Theory
@@ -183,13 +195,8 @@ public void testDirtySubmoduleIndexAndWorktree(IgnoreSubmoduleMode mode)
IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
new FileTreeIterator(db));
indexDiff.setIgnoreSubmoduleMode(mode);
- if (mode.equals(IgnoreSubmoduleMode.ALL)
- || mode.equals(IgnoreSubmoduleMode.DIRTY))
- assertFalse("diff should be false with mode=" + mode,
- indexDiff.diff());
- else
- assertTrue("diff should be true with mode=" + mode,
- indexDiff.diff());
+ assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL,
+ IgnoreSubmoduleMode.DIRTY);
}
@Theory
@@ -200,13 +207,7 @@ public void testDirtySubmoduleWorktreeUntracked(IgnoreSubmoduleMode mode)
IndexDiff indexDiff = new IndexDiff(db, Constants.HEAD,
new FileTreeIterator(db));
indexDiff.setIgnoreSubmoduleMode(mode);
- if (mode.equals(IgnoreSubmoduleMode.ALL)
- || mode.equals(IgnoreSubmoduleMode.DIRTY)
- || mode.equals(IgnoreSubmoduleMode.UNTRACKED))
- assertFalse("diff should be false with mode=" + mode,
- indexDiff.diff());
- else
- assertTrue("diff should be true with mode=" + mode,
- indexDiff.diff());
+ assertDiff(indexDiff, mode, IgnoreSubmoduleMode.ALL,
+ IgnoreSubmoduleMode.DIRTY, IgnoreSubmoduleMode.UNTRACKED);
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
index 7d298ed..476c4e5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java
@@ -45,6 +45,7 @@
package org.eclipse.jgit.lib;
import static java.lang.Integer.valueOf;
+import static org.eclipse.jgit.junit.JGitTestUtil.concat;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
import static org.eclipse.jgit.lib.Constants.OBJ_BAD;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
@@ -62,6 +63,7 @@
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.NULL_SHA1;
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED;
import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE;
+import static org.eclipse.jgit.util.RawParseUtils.decode;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
@@ -72,11 +74,52 @@
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
public class ObjectCheckerTest {
+ private static final ObjectChecker SECRET_KEY_CHECKER = new ObjectChecker() {
+ @Override
+ public void checkBlob(byte[] raw) throws CorruptObjectException {
+ String in = decode(raw);
+ if (in.contains("secret_key")) {
+ throw new CorruptObjectException("don't add a secret key");
+ }
+ }
+ };
+
+ private static final ObjectChecker SECRET_KEY_BLOB_CHECKER = new ObjectChecker() {
+ @Override
+ public BlobObjectChecker newBlobObjectChecker() {
+ return new BlobObjectChecker() {
+ private boolean containSecretKey;
+
+ @Override
+ public void update(byte[] in, int offset, int len) {
+ String str = decode(in, offset, offset + len);
+ if (str.contains("secret_key")) {
+ containSecretKey = true;
+ }
+ }
+
+ @Override
+ public void endBlob(AnyObjectId id)
+ throws CorruptObjectException {
+ if (containSecretKey) {
+ throw new CorruptObjectException(
+ "don't add a secret key");
+ }
+ }
+ };
+ }
+ };
+
private ObjectChecker checker;
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
@Before
public void setUp() throws Exception {
checker = new ObjectChecker();
@@ -101,6 +144,32 @@ public void testCheckBlob() throws CorruptObjectException {
}
@Test
+ public void testCheckBlobNotCorrupt() throws CorruptObjectException {
+ SECRET_KEY_CHECKER.check(OBJ_BLOB, encodeASCII("key = \"public_key\""));
+ }
+
+ @Test
+ public void testCheckBlobCorrupt() throws CorruptObjectException {
+ thrown.expect(CorruptObjectException.class);
+ SECRET_KEY_CHECKER.check(OBJ_BLOB, encodeASCII("key = \"secret_key\""));
+ }
+
+ @Test
+ public void testCheckBlobWithBlobObjectCheckerNotCorrupt()
+ throws CorruptObjectException {
+ SECRET_KEY_BLOB_CHECKER.check(OBJ_BLOB,
+ encodeASCII("key = \"public_key\""));
+ }
+
+ @Test
+ public void testCheckBlobWithBlobObjectCheckerCorrupt()
+ throws CorruptObjectException {
+ thrown.expect(CorruptObjectException.class);
+ SECRET_KEY_BLOB_CHECKER.check(OBJ_BLOB,
+ encodeASCII("key = \"secret_key\""));
+ }
+
+ @Test
public void testValidCommitNoParent() throws CorruptObjectException {
StringBuilder b = new StringBuilder();
@@ -1160,20 +1229,7 @@ public void testInvalidTreeNameIsMacHFSGit3()
checker.checkTree(data);
}
- private static byte[] concat(byte[]... b) {
- int n = 0;
- for (byte[] a : b) {
- n += a.length;
- }
- byte[] data = new byte[n];
- n = 0;
- for (byte[] a : b) {
- System.arraycopy(a, 0, data, n, a.length);
- n += a.length;
- }
- return data;
- }
@Test
public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd()
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java
index 7db9f60..15f28af 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogResolveTest.java
@@ -179,4 +179,4 @@ public void resolveDate() throws Exception {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SubmoduleConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SubmoduleConfigTest.java
new file mode 100644
index 0000000..fb8dec5
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SubmoduleConfigTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017, David Pursehouse <david.pursehouse@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode;
+import org.junit.Test;
+
+public class SubmoduleConfigTest {
+ @Test
+ public void fetchRecurseMatch() throws Exception {
+ assertTrue(FetchRecurseSubmodulesMode.YES.matchConfigValue("yes"));
+ assertTrue(FetchRecurseSubmodulesMode.YES.matchConfigValue("YES"));
+ assertTrue(FetchRecurseSubmodulesMode.YES.matchConfigValue("true"));
+ assertTrue(FetchRecurseSubmodulesMode.YES.matchConfigValue("TRUE"));
+
+ assertTrue(FetchRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("on-demand"));
+ assertTrue(FetchRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("ON-DEMAND"));
+ assertTrue(FetchRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("on_demand"));
+ assertTrue(FetchRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("ON_DEMAND"));
+
+ assertTrue(FetchRecurseSubmodulesMode.NO.matchConfigValue("no"));
+ assertTrue(FetchRecurseSubmodulesMode.NO.matchConfigValue("NO"));
+ assertTrue(FetchRecurseSubmodulesMode.NO.matchConfigValue("false"));
+ assertTrue(FetchRecurseSubmodulesMode.NO.matchConfigValue("FALSE"));
+ }
+
+ @Test
+ public void fetchRecurseNoMatch() throws Exception {
+ assertFalse(FetchRecurseSubmodulesMode.YES.matchConfigValue("Y"));
+ assertFalse(FetchRecurseSubmodulesMode.NO.matchConfigValue("N"));
+ assertFalse(FetchRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("ONDEMAND"));
+ assertFalse(FetchRecurseSubmodulesMode.YES.matchConfigValue(""));
+ assertFalse(FetchRecurseSubmodulesMode.YES.matchConfigValue(null));
+ }
+
+ @Test
+ public void fetchRecurseToConfigValue() {
+ assertEquals("on-demand",
+ FetchRecurseSubmodulesMode.ON_DEMAND.toConfigValue());
+ assertEquals("true", FetchRecurseSubmodulesMode.YES.toConfigValue());
+ assertEquals("false", FetchRecurseSubmodulesMode.NO.toConfigValue());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java
index 2451c50..077645e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java
@@ -171,4 +171,4 @@ public void testInconsistentCommitTimes() throws Exception {
assertNull(rw.next());
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java
index 353a487..cf02aa8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/SkipRevFilterTest.java
@@ -81,4 +81,4 @@ public void testSkipRevFilter0() throws Exception {
public void testSkipRevFilterNegative() throws Exception {
SkipRevFilter.create(-1);
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java
index 61df9d9..5832518 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleStatusTest.java
@@ -59,11 +59,11 @@
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.storage.file.FileBasedConfig;
@@ -256,11 +256,16 @@ public void apply(DirCacheEntry ent) {
}
@Test
- public void repositoryWithInitializedSubmodule() throws IOException,
- GitAPIException {
- final ObjectId id = ObjectId
- .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
- final String path = "sub";
+ public void repositoryWithInitializedSubmodule() throws Exception {
+ String path = "sub";
+ Repository subRepo = Git.init().setBare(false)
+ .setDirectory(new File(db.getWorkTree(), path)).call()
+ .getRepository();
+ assertNotNull(subRepo);
+
+ TestRepository<?> subTr = new TestRepository<>(subRepo);
+ ObjectId id = subTr.branch(Constants.HEAD).commit().create().copy();
+
DirCache cache = db.lockDirCache();
DirCacheEditor editor = cache.editor();
editor.add(new PathEdit(path) {
@@ -287,15 +292,6 @@ public void apply(DirCacheEntry ent) {
ConfigConstants.CONFIG_KEY_URL, url);
modulesConfig.save();
- Repository subRepo = Git.init().setBare(false)
- .setDirectory(new File(db.getWorkTree(), path)).call()
- .getRepository();
- assertNotNull(subRepo);
-
- RefUpdate update = subRepo.updateRef(Constants.HEAD, true);
- update.setNewObjectId(id);
- update.forceUpdate();
-
SubmoduleStatusCommand command = new SubmoduleStatusCommand(db);
Map<String, SubmoduleStatus> statuses = command.call();
assertNotNull(statuses);
@@ -312,11 +308,16 @@ public void apply(DirCacheEntry ent) {
}
@Test
- public void repositoryWithDifferentRevCheckedOutSubmodule()
- throws IOException, GitAPIException {
- final ObjectId id = ObjectId
- .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
- final String path = "sub";
+ public void repositoryWithDifferentRevCheckedOutSubmodule() throws Exception {
+ String path = "sub";
+ Repository subRepo = Git.init().setBare(false)
+ .setDirectory(new File(db.getWorkTree(), path)).call()
+ .getRepository();
+ assertNotNull(subRepo);
+
+ TestRepository<?> subTr = new TestRepository<>(subRepo);
+ ObjectId id = subTr.branch(Constants.HEAD).commit().create().copy();
+
DirCache cache = db.lockDirCache();
DirCacheEditor editor = cache.editor();
editor.add(new PathEdit(path) {
@@ -343,15 +344,7 @@ public void apply(DirCacheEntry ent) {
ConfigConstants.CONFIG_KEY_URL, url);
modulesConfig.save();
- Repository subRepo = Git.init().setBare(false)
- .setDirectory(new File(db.getWorkTree(), path)).call()
- .getRepository();
- assertNotNull(subRepo);
-
- RefUpdate update = subRepo.updateRef(Constants.HEAD, true);
- update.setNewObjectId(ObjectId
- .fromString("aaaa0000aaaa0000aaaa0000aaaa0000aaaa0000"));
- update.forceUpdate();
+ ObjectId newId = subTr.branch(Constants.HEAD).commit().create().copy();
SubmoduleStatusCommand command = new SubmoduleStatusCommand(db);
Map<String, SubmoduleStatus> statuses = command.call();
@@ -365,7 +358,7 @@ public void apply(DirCacheEntry ent) {
assertNotNull(status);
assertEquals(path, status.getPath());
assertEquals(id, status.getIndexId());
- assertEquals(update.getNewObjectId(), status.getHeadId());
+ assertEquals(newId, status.getHeadId());
assertEquals(SubmoduleStatusType.REV_CHECKED_OUT, status.getType());
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java
index 8998a85..fed22c0 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleWalkTest.java
@@ -444,4 +444,44 @@ public void apply(DirCacheEntry ent) {
assertNull(gen.getRepository());
assertFalse(gen.next());
}
+
+ @Test
+ public void testTreeIteratorWithGitmodulesNameNotPath() throws Exception {
+ final ObjectId subId = ObjectId
+ .fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
+ final String path = "sub";
+ final String arbitraryName = "x";
+
+ final Config gitmodules = new Config();
+ gitmodules.setString(CONFIG_SUBMODULE_SECTION, arbitraryName,
+ CONFIG_KEY_PATH, "sub");
+ gitmodules.setString(CONFIG_SUBMODULE_SECTION, arbitraryName,
+ CONFIG_KEY_URL, "git://example.com/sub");
+
+ RevCommit commit = testDb.getRevWalk()
+ .parseCommit(testDb.commit().noParents()
+ .add(DOT_GIT_MODULES, gitmodules.toText())
+ .edit(new PathEdit(path) {
+
+ @Override
+ public void apply(DirCacheEntry ent) {
+ ent.setFileMode(FileMode.GITLINK);
+ ent.setObjectId(subId);
+ }
+ }).create());
+
+ final CanonicalTreeParser p = new CanonicalTreeParser();
+ p.reset(testDb.getRevWalk().getObjectReader(), commit.getTree());
+ SubmoduleWalk gen = SubmoduleWalk.forPath(db, p, "sub");
+ assertEquals(path, gen.getPath());
+ assertEquals(subId, gen.getObjectId());
+ assertEquals(new File(db.getWorkTree(), path), gen.getDirectory());
+ assertNull(gen.getConfigUpdate());
+ assertNull(gen.getConfigUrl());
+ assertEquals("sub", gen.getModulesPath());
+ assertNull(gen.getModulesUpdate());
+ assertEquals("git://example.com/sub", gen.getModulesUrl());
+ assertNull(gen.getRepository());
+ assertFalse(gen.next());
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/DaemonTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/DaemonTest.java
new file mode 100644
index 0000000..a5e5441
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/DaemonTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.net.InetSocketAddress;
+
+import org.junit.Test;
+
+/**
+ * Daemon tests.
+ */
+public class DaemonTest {
+
+ @Test
+ public void testDaemonStop() throws Exception {
+ Daemon d = new Daemon();
+ d.start();
+ InetSocketAddress address = d.getAddress();
+ assertTrue("Port should be allocated", address.getPort() > 0);
+ assertTrue("Daemon should be running", d.isRunning());
+ Thread.sleep(1000); // Give it time to enter accept()
+ d.stopAndWait();
+ // Try to start a new Daemon again on the same port
+ d = new Daemon(address);
+ d.start();
+ InetSocketAddress newAddress = d.getAddress();
+ assertEquals("New daemon should run on the same port", address,
+ newAddress);
+ assertTrue("Daemon should be running", d.isRunning());
+ Thread.sleep(1000);
+ d.stopAndWait();
+ }
+
+ @Test
+ public void testDaemonRestart() throws Exception {
+ Daemon d = new Daemon();
+ d.start();
+ InetSocketAddress address = d.getAddress();
+ assertTrue("Port should be allocated", address.getPort() > 0);
+ assertTrue("Daemon should be running", d.isRunning());
+ Thread.sleep(1000);
+ d.stopAndWait();
+ // Re-start the same daemon
+ d.start();
+ InetSocketAddress newAddress = d.getAddress();
+ assertEquals("Daemon should again run on the same port", address,
+ newAddress);
+ assertTrue("Daemon should be running", d.isRunning());
+ Thread.sleep(1000);
+ d.stopAndWait();
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java
new file mode 100644
index 0000000..c6b016a
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.jgit.lib.Config;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for correctly resolving URIs when reading http.* values from a
+ * {@link Config}.
+ */
+public class HttpConfigTest {
+
+ private static final String DEFAULT = "[http]\n" + "\tpostBuffer = 1\n"
+ + "\tsslVerify= true\n" + "\tfollowRedirects = true\n"
+ + "\tmaxRedirects = 5\n\n";
+
+ private Config config;
+
+ @Before
+ public void setUp() {
+ config = new Config();
+ }
+
+ @Test
+ public void testDefault() throws Exception {
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1024 * 1024, http.getPostBuffer());
+ assertTrue(http.isSslVerify());
+ assertEquals(HttpConfig.HttpRedirectMode.INITIAL,
+ http.getFollowRedirects());
+ }
+
+ @Test
+ public void testMatchSuccess() throws Exception {
+ config.fromText(DEFAULT + "[http \"http://example.com\"]\n"
+ + "\tpostBuffer = 1024\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("https://example.com/path/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://example.org/path/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://example.com:80/path/repo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://example.com:8080/path/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchWithOnlySchemeInConfig() throws Exception {
+ config.fromText(
+ DEFAULT + "[http \"http://\"]\n" + "\tpostBuffer = 1024\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchWithPrefixUriInConfig() throws Exception {
+ config.fromText(DEFAULT + "[http \"http://example\"]\n"
+ + "\tpostBuffer = 1024\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchCaseSensitivity() throws Exception {
+ config.fromText(DEFAULT + "[http \"http://exAMPle.com\"]\n"
+ + "\tpostBuffer = 1024\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchWithInvalidUriInConfig() throws Exception {
+ config.fromText(
+ DEFAULT + "[http \"///\"]\n" + "\tpostBuffer = 1024\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchWithInvalidAndValidUriInConfig() throws Exception {
+ config.fromText(DEFAULT + "[http \"///\"]\n" + "\tpostBuffer = 1024\n"
+ + "[http \"http://example.com\"]\n" + "\tpostBuffer = 2048\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(2048, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchWithHostEndingInSlash() throws Exception {
+ config.fromText(DEFAULT + "[http \"http://example.com/\"]\n"
+ + "\tpostBuffer = 1024\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchWithUser() throws Exception {
+ config.fromText(DEFAULT + "[http \"http://example.com/path\"]\n"
+ + "\tpostBuffer = 1024\n"
+ + "[http \"http://example.com/path/repo\"]\n"
+ + "\tpostBuffer = 2048\n"
+ + "[http \"http://user@example.com/path\"]\n"
+ + "\tpostBuffer = 4096\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://user@example.com/path/repo.git"));
+ assertEquals(4096, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://user@example.com/path/repo/foo.git"));
+ assertEquals(2048, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://user@example.com/path/foo.git"));
+ assertEquals(4096, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://example.com/path/foo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://User@example.com/path/repo/foo.git"));
+ assertEquals(2048, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://User@example.com/path/foo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ }
+
+ @Test
+ public void testMatchLonger() throws Exception {
+ config.fromText(DEFAULT + "[http \"http://example.com/path\"]\n"
+ + "\tpostBuffer = 1024\n"
+ + "[http \"http://example.com/path/repo\"]\n"
+ + "\tpostBuffer = 2048\n");
+ HttpConfig http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo.git"));
+ assertEquals(1024, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://example.com/foo/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("https://example.com/path/repo.git"));
+ assertEquals(1, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://example.com/path/repo/.git"));
+ assertEquals(2048, http.getPostBuffer());
+ http = new HttpConfig(config, new URIish("http://example.com/path"));
+ assertEquals(1024, http.getPostBuffer());
+ http = new HttpConfig(config,
+ new URIish("http://user@example.com/path"));
+ assertEquals(1024, http.getPostBuffer());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigUriPathTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigUriPathTest.java
new file mode 100644
index 0000000..94de2f2
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpConfigUriPathTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+/**
+ * Basic URI path prefix match tests for {@link HttpConfig}.
+ */
+public class HttpConfigUriPathTest {
+
+ @Test
+ public void testNormalizationEmptyPaths() {
+ assertEquals("/", HttpConfig.normalize(""));
+ assertEquals("/", HttpConfig.normalize("/"));
+ }
+
+ @Test
+ public void testNormalization() {
+ assertEquals("/f", HttpConfig.normalize("f"));
+ assertEquals("/f", HttpConfig.normalize("/f"));
+ assertEquals("/f/", HttpConfig.normalize("/f/"));
+ assertEquals("/foo", HttpConfig.normalize("foo"));
+ assertEquals("/foo", HttpConfig.normalize("/foo"));
+ assertEquals("/foo/", HttpConfig.normalize("/foo/"));
+ assertEquals("/foo/bar", HttpConfig.normalize("foo/bar"));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar"));
+ assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/"));
+ }
+
+ @Test
+ public void testNormalizationWithDot() {
+ assertEquals("/", HttpConfig.normalize("."));
+ assertEquals("/", HttpConfig.normalize("/."));
+ assertEquals("/", HttpConfig.normalize("/./"));
+ assertEquals("/foo", HttpConfig.normalize("foo/."));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo/./bar"));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar/."));
+ assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/./"));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo/./././bar"));
+ assertEquals("/foo/bar/", HttpConfig.normalize("/foo/./././bar/"));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar/././."));
+ assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/./././"));
+ assertEquals("/foo/bar/.baz/bam",
+ HttpConfig.normalize("/foo/bar/.baz/bam"));
+ assertEquals("/foo/bar/.baz/bam/",
+ HttpConfig.normalize("/foo/bar/.baz/bam/"));
+ }
+
+ @Test
+ public void testNormalizationWithDotDot() {
+ assertEquals("/", HttpConfig.normalize("foo/.."));
+ assertEquals("/", HttpConfig.normalize("/foo/.."));
+ assertEquals("/", HttpConfig.normalize("/foo/../bar/.."));
+ assertEquals("/", HttpConfig.normalize("/foo/.././bar/.."));
+ assertEquals("/bar", HttpConfig.normalize("foo/../bar"));
+ assertEquals("/bar", HttpConfig.normalize("/foo/../bar"));
+ assertEquals("/bar", HttpConfig.normalize("/foo/./.././bar"));
+ assertEquals("/bar/", HttpConfig.normalize("/foo/../bar/"));
+ assertEquals("/bar/", HttpConfig.normalize("/foo/./.././bar/"));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo/bar/baz/.."));
+ assertEquals("/foo/bar/", HttpConfig.normalize("/foo/bar/baz/../"));
+ assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/../.."));
+ assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/../.."));
+ assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/.././.."));
+ assertEquals("/foo", HttpConfig.normalize("/foo/bar/baz/../././.."));
+ assertEquals("/foo/baz", HttpConfig.normalize("/foo/bar/../baz"));
+ assertEquals("/foo/baz/", HttpConfig.normalize("/foo/bar/../baz/"));
+ assertEquals("/foo/baz", HttpConfig.normalize("/foo/bar/../baz/."));
+ assertEquals("/foo/baz/", HttpConfig.normalize("/foo/bar/../baz/./"));
+ assertEquals("/foo", HttpConfig.normalize("/foo/bar/../baz/.."));
+ assertEquals("/foo/", HttpConfig.normalize("/foo/bar/../baz/../"));
+ assertEquals("/baz", HttpConfig.normalize("/foo/bar/../../baz"));
+ assertEquals("/baz/", HttpConfig.normalize("/foo/bar/../../baz/"));
+ assertEquals("/foo/.b/bar", HttpConfig.normalize("/foo/.b/bar"));
+ assertEquals("/.f/foo/.b/bar/", HttpConfig.normalize(".f/foo/.b/bar/"));
+ assertEquals("/foo/bar/..baz/bam",
+ HttpConfig.normalize("/foo/bar/..baz/bam"));
+ assertEquals("/foo/bar/..baz/bam/",
+ HttpConfig.normalize("/foo/bar/..baz/bam/"));
+ assertEquals("/foo/bar/.../baz/bam",
+ HttpConfig.normalize("/foo/bar/.../baz/bam"));
+ assertEquals("/foo/bar/.../baz/bam/",
+ HttpConfig.normalize("/foo/bar/.../baz/bam/"));
+ }
+
+ @Test
+ public void testNormalizationWithDoubleSlash() {
+ assertEquals("/", HttpConfig.normalize("//"));
+ assertEquals("/foo/", HttpConfig.normalize("///foo//"));
+ assertEquals("/foo", HttpConfig.normalize("///foo//."));
+ assertEquals("/foo/", HttpConfig.normalize("///foo//.////"));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo//bar"));
+ assertEquals("/foo/bar", HttpConfig.normalize("/foo//bar//."));
+ assertEquals("/foo/bar/", HttpConfig.normalize("/foo//bar//./"));
+ }
+
+ @Test
+ public void testNormalizationWithDotDotFailing() {
+ assertNull(HttpConfig.normalize(".."));
+ assertNull(HttpConfig.normalize("/.."));
+ assertNull(HttpConfig.normalize("/../"));
+ assertNull(HttpConfig.normalize("/../foo"));
+ assertNull(HttpConfig.normalize("./../foo"));
+ assertNull(HttpConfig.normalize("/./../foo"));
+ assertNull(HttpConfig.normalize("/foo/./.././.."));
+ assertNull(HttpConfig.normalize("/foo/../bar/../.."));
+ assertNull(HttpConfig.normalize("/foo/../bar/../../baz"));
+ }
+
+ @Test
+ public void testSegmentCompare() {
+ // 2nd parameter is the match, will be normalized
+ assertSuccess("/foo", "");
+ assertSuccess("/foo", "/");
+ assertSuccess("/foo", "//");
+ assertSuccess("/foo", "foo");
+ assertSuccess("/foo", "/foo");
+ assertSuccess("/foo/", "foo");
+ assertSuccess("/foo/", "/foo");
+ assertSuccess("/foo/", "foo/");
+ assertSuccess("/foo/", "/foo/");
+ assertSuccess("/foo/bar", "foo");
+ assertSuccess("/foo/bar", "foo/");
+ assertSuccess("/foo/bar", "foo/bar");
+ assertSuccess("/foo/bar/", "foo/bar");
+ assertSuccess("/foo/bar/", "foo/bar/");
+ assertSuccess("/foo/bar", "/foo/bar");
+ assertSuccess("/foo/bar/", "/foo/bar");
+ assertSuccess("/foo/bar/", "/foo/bar/");
+ assertSuccess("/foo/bar", "/foo/bar/..");
+ assertSuccess("/foo/bar/", "/foo/bar/..");
+ assertSuccess("/foo/bar/", "/foo/bar/../");
+ assertSuccess("/foo/bar", "/foo/./bar");
+ assertSuccess("/foo/bar/", "/foo/./bar/");
+ assertSuccess("/some/repo/.git", "/some/repo");
+ assertSuccess("/some/repo/bare.git", "/some/repo");
+ assertSuccess("/some/repo/.git", "/some/repo/.git");
+ assertSuccess("/some/repo/bare.git", "/some/repo/bare.git");
+ }
+
+ @Test
+ public void testSegmentCompareFailing() {
+ // 2nd parameter is the match, will be normalized
+ assertEquals(-1, HttpConfig.segmentCompare("/foo", "foo/"));
+ assertEquals(-1, HttpConfig.segmentCompare("/foo", "/foo/"));
+ assertEquals(-1, HttpConfig.segmentCompare("/foobar", "foo"));
+ assertEquals(-1, HttpConfig.segmentCompare("/foobar", "/foo"));
+ assertEquals(-1,
+ HttpConfig.segmentCompare("/foo/barbar/baz", "foo/bar"));
+ assertEquals(-1, HttpConfig.segmentCompare("/foo/barbar", "/foo/bar"));
+ assertEquals(-1,
+ HttpConfig.segmentCompare("/some/repo.git", "/some/repo"));
+ assertEquals(-1,
+ HttpConfig.segmentCompare("/some/repo.git", "/some/repo.g"));
+ assertEquals(-1, HttpConfig.segmentCompare("/some/repo/bare.git",
+ "/some/repo/bar"));
+ assertSuccess("/some/repo/bare.git", "/some/repo");
+ // Just to make sure we don't use the PathMatchers...
+ assertEquals(-1, HttpConfig.segmentCompare("/foo/barbar/baz", "**"));
+ assertEquals(-1,
+ HttpConfig.segmentCompare("/foo/barbar/baz", "**/foo"));
+ assertEquals(-1,
+ HttpConfig.segmentCompare("/foo/barbar/baz", "/*/barbar/**"));
+ assertEquals(-1, HttpConfig.segmentCompare("/foo", "/*"));
+ assertEquals(-1, HttpConfig.segmentCompare("/foo", "/???"));
+ assertEquals(-1, HttpConfig.segmentCompare("/foo/bar/baz", "bar"));
+ // Failing to normalize
+ assertEquals(-1,
+ HttpConfig.segmentCompare("/foo/bar/baz", "bar/../.."));
+ }
+
+ private void assertSuccess(String uri, String match) {
+ String normalized = HttpConfig.normalize(match);
+ assertEquals(normalized.length(),
+ HttpConfig.segmentCompare(uri, match));
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JschConfigSessionFactoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JschConfigSessionFactoryTest.java
new file mode 100644
index 0000000..1e65a20
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/JschConfigSessionFactoryTest.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.transport;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.util.FS;
+import org.junit.After;
+import org.junit.Test;
+
+import com.jcraft.jsch.Session;
+
+/**
+ * Tests for correctly interpreting ssh config values when Jsch sessions are
+ * used.
+ */
+public class JschConfigSessionFactoryTest {
+
+ File tmpConfigFile;
+
+ OpenSshConfig tmpConfig;
+
+ DefaultSshSessionFactory factory = new DefaultSshSessionFactory();
+
+ @After
+ public void removeTmpConfig() {
+ if (tmpConfigFile == null) {
+ return;
+ }
+ if (tmpConfigFile.exists() && !tmpConfigFile.delete()) {
+ tmpConfigFile.deleteOnExit();
+ }
+ tmpConfigFile = null;
+ }
+
+ @Test
+ public void testNoConfigEntry() throws Exception {
+ tmpConfigFile = File.createTempFile("jsch", "test");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://egit/egit/egit");
+ assertEquals("egit", session.getHost());
+ // No user in URI, none in ssh config: default is OS user name
+ assertEquals(System.getProperty("user.name"), session.getUserName());
+ assertEquals(22, session.getPort());
+ }
+
+ @Test
+ public void testAlias() throws Exception {
+ tmpConfigFile = createConfig("Host egit", "Hostname git.eclipse.org",
+ "User foo", "Port 29418");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://egit/egit/egit");
+ assertEquals("git.eclipse.org", session.getHost());
+ assertEquals("foo", session.getUserName());
+ assertEquals(29418, session.getPort());
+ }
+
+ @Test
+ public void testAliasWithUser() throws Exception {
+ tmpConfigFile = createConfig("Host egit", "Hostname git.eclipse.org",
+ "User foo", "Port 29418");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://bar@egit/egit/egit");
+ assertEquals("git.eclipse.org", session.getHost());
+ assertEquals("bar", session.getUserName());
+ assertEquals(29418, session.getPort());
+ }
+
+ @Test
+ public void testAliasWithPort() throws Exception {
+ tmpConfigFile = createConfig("Host egit", "Hostname git.eclipse.org",
+ "User foo", "Port 29418");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://bar@egit:22/egit/egit");
+ assertEquals("git.eclipse.org", session.getHost());
+ assertEquals("bar", session.getUserName());
+ assertEquals(22, session.getPort());
+ }
+
+ @Test
+ public void testAliasIdentical() throws Exception {
+ tmpConfigFile = createConfig("Host git.eclipse.org",
+ "Hostname git.eclipse.org", "User foo", "Port 29418");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://git.eclipse.org/egit/egit");
+ assertEquals("git.eclipse.org", session.getHost());
+ assertEquals("foo", session.getUserName());
+ assertEquals(29418, session.getPort());
+ }
+
+ @Test
+ public void testAliasIdenticalWithUser() throws Exception {
+ tmpConfigFile = createConfig("Host git.eclipse.org",
+ "Hostname git.eclipse.org", "User foo", "Port 29418");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://bar@git.eclipse.org/egit/egit");
+ assertEquals("git.eclipse.org", session.getHost());
+ assertEquals("bar", session.getUserName());
+ assertEquals(29418, session.getPort());
+ }
+
+ @Test
+ public void testAliasIdenticalWithPort() throws Exception {
+ tmpConfigFile = createConfig("Host git.eclipse.org",
+ "Hostname git.eclipse.org", "User foo", "Port 29418");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession(
+ "ssh://bar@git.eclipse.org:300/egit/egit");
+ assertEquals("git.eclipse.org", session.getHost());
+ assertEquals("bar", session.getUserName());
+ assertEquals(300, session.getPort());
+ }
+
+ @Test
+ public void testConnectTimout() throws Exception {
+ tmpConfigFile = createConfig("Host git.eclipse.org",
+ "Hostname git.eclipse.org", "User foo", "Port 29418",
+ "ConnectTimeout 10");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://git.eclipse.org/something");
+ assertEquals("git.eclipse.org", session.getHost());
+ assertEquals("foo", session.getUserName());
+ assertEquals(29418, session.getPort());
+ assertEquals(TimeUnit.SECONDS.toMillis(10), session.getTimeout());
+ }
+
+ @Test
+ public void testAliasCaseDifferenceUpcase() throws Exception {
+ tmpConfigFile = createConfig("Host Bitbucket.org",
+ "Hostname bitbucket.org", "User foo", "Port 29418",
+ "ConnectTimeout 10", //
+ "Host bitbucket.org", "Hostname bitbucket.org", "User bar",
+ "Port 22", "ConnectTimeout 5");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://Bitbucket.org/something");
+ assertEquals("bitbucket.org", session.getHost());
+ assertEquals("foo", session.getUserName());
+ assertEquals(29418, session.getPort());
+ assertEquals(TimeUnit.SECONDS.toMillis(10), session.getTimeout());
+ }
+
+ @Test
+ public void testAliasCaseDifferenceLowcase() throws Exception {
+ tmpConfigFile = createConfig("Host Bitbucket.org",
+ "Hostname bitbucket.org", "User foo", "Port 29418",
+ "ConnectTimeout 10", //
+ "Host bitbucket.org", "Hostname bitbucket.org", "User bar",
+ "Port 22", "ConnectTimeout 5");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://bitbucket.org/something");
+ assertEquals("bitbucket.org", session.getHost());
+ assertEquals("bar", session.getUserName());
+ assertEquals(22, session.getPort());
+ assertEquals(TimeUnit.SECONDS.toMillis(5), session.getTimeout());
+ }
+
+ @Test
+ public void testAliasCaseDifferenceUpcaseInverted() throws Exception {
+ tmpConfigFile = createConfig("Host bitbucket.org",
+ "Hostname bitbucket.org", "User bar", "Port 22",
+ "ConnectTimeout 5", //
+ "Host Bitbucket.org", "Hostname bitbucket.org", "User foo",
+ "Port 29418", "ConnectTimeout 10");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://Bitbucket.org/something");
+ assertEquals("bitbucket.org", session.getHost());
+ assertEquals("foo", session.getUserName());
+ assertEquals(29418, session.getPort());
+ assertEquals(TimeUnit.SECONDS.toMillis(10), session.getTimeout());
+ }
+
+ @Test
+ public void testAliasCaseDifferenceLowcaseInverted() throws Exception {
+ tmpConfigFile = createConfig("Host bitbucket.org",
+ "Hostname bitbucket.org", "User bar", "Port 22",
+ "ConnectTimeout 5", //
+ "Host Bitbucket.org", "Hostname bitbucket.org", "User foo",
+ "Port 29418", "ConnectTimeout 10");
+ tmpConfig = new OpenSshConfig(tmpConfigFile.getParentFile(),
+ tmpConfigFile);
+ factory.setConfig(tmpConfig);
+ Session session = createSession("ssh://bitbucket.org/something");
+ assertEquals("bitbucket.org", session.getHost());
+ assertEquals("bar", session.getUserName());
+ assertEquals(22, session.getPort());
+ assertEquals(TimeUnit.SECONDS.toMillis(5), session.getTimeout());
+ }
+
+ private File createConfig(String... lines) throws Exception {
+ File f = File.createTempFile("jsch", "test");
+ Files.write(f.toPath(), Arrays.asList(lines));
+ return f;
+ }
+
+ private Session createSession(String uriText) throws Exception {
+ // For this test to make sense, these few lines must correspond to the
+ // code in JschConfigSessionFactory.getSession(). Because of
+ // side-effects we cannot encapsulate that there properly and so we have
+ // to duplicate this bit here. We also can't test getSession() itself
+ // since it would try to actually connect to a server.
+ URIish uri = new URIish(uriText);
+ String host = uri.getHost();
+ String user = uri.getUser();
+ String password = uri.getPass();
+ int port = uri.getPort();
+ OpenSshConfig.Host hostConfig = tmpConfig.lookup(host);
+ if (port <= 0) {
+ port = hostConfig.getPort();
+ }
+ if (user == null) {
+ user = hostConfig.getUser();
+ }
+ return factory.createSession(null, FS.DETECTED, user, password, host,
+ port, hostConfig);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
index fc520ab..d604751 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008, 2014 Google Inc.
+ * Copyright (C) 2008, 2017 Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -43,10 +43,13 @@
package org.eclipse.jgit.transport;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.io.File;
@@ -58,9 +61,12 @@
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.transport.OpenSshConfig.Host;
import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.SystemReader;
import org.junit.Before;
import org.junit.Test;
+import com.jcraft.jsch.ConfigRepository;
+
public class OpenSshConfigTest extends RepositoryTestCase {
private File home;
@@ -79,15 +85,18 @@ public void setUp() throws Exception {
configFile = new File(new File(home, ".ssh"), Constants.CONFIG);
FileUtils.mkdir(configFile.getParentFile());
- System.setProperty("user.name", "jex_junit");
+ mockSystemReader.setProperty(Constants.OS_USER_NAME_KEY, "jex_junit");
osc = new OpenSshConfig(home, configFile);
}
private void config(final String data) throws IOException {
- final OutputStreamWriter fw = new OutputStreamWriter(
- new FileOutputStream(configFile), "UTF-8");
- fw.write(data);
- fw.close();
+ long lastMtime = configFile.lastModified();
+ do {
+ try (final OutputStreamWriter fw = new OutputStreamWriter(
+ new FileOutputStream(configFile), "UTF-8")) {
+ fw.write(data);
+ }
+ } while (lastMtime == configFile.lastModified());
}
@Test
@@ -155,13 +164,18 @@ public void testQuoteParsing() throws Exception {
@Test
public void testAlias_DoesNotMatch() throws Exception {
- config("Host orcz\n" + "\tHostName repo.or.cz\n");
+ config("Host orcz\n" + "Port 29418\n" + "\tHostName repo.or.cz\n");
final Host h = osc.lookup("repo.or.cz");
assertNotNull(h);
assertEquals("repo.or.cz", h.getHostName());
assertEquals("jex_junit", h.getUser());
assertEquals(22, h.getPort());
assertNull(h.getIdentityFile());
+ final Host h2 = osc.lookup("orcz");
+ assertEquals("repo.or.cz", h.getHostName());
+ assertEquals("jex_junit", h.getUser());
+ assertEquals(29418, h2.getPort());
+ assertNull(h.getIdentityFile());
}
@Test
@@ -282,4 +296,198 @@ public void testAlias_badConnectionAttempts() throws Exception {
assertNotNull(h);
assertEquals(1, h.getConnectionAttempts());
}
+
+ @Test
+ public void testDefaultBlock() throws Exception {
+ config("ConnectionAttempts 5\n\nHost orcz\nConnectionAttempts 3\n");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals(5, h.getConnectionAttempts());
+ }
+
+ @Test
+ public void testHostCaseInsensitive() throws Exception {
+ config("hOsT orcz\nConnectionAttempts 3\n");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals(3, h.getConnectionAttempts());
+ }
+
+ @Test
+ public void testListValueSingle() throws Exception {
+ config("Host orcz\nUserKnownHostsFile /foo/bar\n");
+ final ConfigRepository.Config c = osc.getConfig("orcz");
+ assertNotNull(c);
+ assertEquals("/foo/bar", c.getValue("UserKnownHostsFile"));
+ }
+
+ @Test
+ public void testListValueMultiple() throws Exception {
+ // Tilde expansion occurs within the parser
+ config("Host orcz\nUserKnownHostsFile \"~/foo/ba z\" /foo/bar \n");
+ final ConfigRepository.Config c = osc.getConfig("orcz");
+ assertNotNull(c);
+ assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
+ "/foo/bar" },
+ c.getValues("UserKnownHostsFile"));
+ }
+
+ @Test
+ public void testRepeatedLookups() throws Exception {
+ config("Host orcz\n" + "\tConnectionAttempts 5\n");
+ final Host h1 = osc.lookup("orcz");
+ final Host h2 = osc.lookup("orcz");
+ assertNotNull(h1);
+ assertSame(h1, h2);
+ assertEquals(5, h1.getConnectionAttempts());
+ assertEquals(h1.getConnectionAttempts(), h2.getConnectionAttempts());
+ final ConfigRepository.Config c = osc.getConfig("orcz");
+ assertNotNull(c);
+ assertSame(c, h1.getConfig());
+ assertSame(c, h2.getConfig());
+ }
+
+ @Test
+ public void testRepeatedLookupsWithModification() throws Exception {
+ config("Host orcz\n" + "\tConnectionAttempts -1\n");
+ final Host h1 = osc.lookup("orcz");
+ assertNotNull(h1);
+ assertEquals(1, h1.getConnectionAttempts());
+ config("Host orcz\n" + "\tConnectionAttempts 5\n");
+ final Host h2 = osc.lookup("orcz");
+ assertNotNull(h2);
+ assertNotSame(h1, h2);
+ assertEquals(5, h2.getConnectionAttempts());
+ assertEquals(1, h1.getConnectionAttempts());
+ assertNotSame(h1.getConfig(), h2.getConfig());
+ }
+
+ @Test
+ public void testIdentityFile() throws Exception {
+ config("Host orcz\nIdentityFile \"~/foo/ba z\"\nIdentityFile /foo/bar");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ File f = h.getIdentityFile();
+ assertNotNull(f);
+ // Host does tilde replacement
+ assertEquals(new File(home, "foo/ba z"), f);
+ final ConfigRepository.Config c = h.getConfig();
+ // Config does tilde replacement, too
+ assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
+ "/foo/bar" },
+ c.getValues("IdentityFile"));
+ }
+
+ @Test
+ public void testMultiIdentityFile() throws Exception {
+ config("IdentityFile \"~/foo/ba z\"\nHost orcz\nIdentityFile /foo/bar\nHOST *\nIdentityFile /foo/baz");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ File f = h.getIdentityFile();
+ assertNotNull(f);
+ // Host does tilde replacement
+ assertEquals(new File(home, "foo/ba z"), f);
+ final ConfigRepository.Config c = h.getConfig();
+ // Config does tilde replacement, too
+ assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
+ "/foo/bar", "/foo/baz" },
+ c.getValues("IdentityFile"));
+ }
+
+ @Test
+ public void testNegatedPattern() throws Exception {
+ config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST !*.or.cz\nIdentityFile /foo/baz");
+ final Host h = osc.lookup("repo.or.cz");
+ assertNotNull(h);
+ assertEquals(new File(home, "foo/bar"), h.getIdentityFile());
+ assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath() },
+ h.getConfig().getValues("IdentityFile"));
+ }
+
+ @Test
+ public void testPattern() throws Exception {
+ config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz");
+ final Host h = osc.lookup("repo.or.cz");
+ assertNotNull(h);
+ assertEquals(new File(home, "foo/bar"), h.getIdentityFile());
+ assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(),
+ "/foo/baz" },
+ h.getConfig().getValues("IdentityFile"));
+ }
+
+ @Test
+ public void testMultiHost() throws Exception {
+ config("Host orcz *.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz");
+ final Host h1 = osc.lookup("repo.or.cz");
+ assertNotNull(h1);
+ assertEquals(new File(home, "foo/bar"), h1.getIdentityFile());
+ assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(),
+ "/foo/baz" },
+ h1.getConfig().getValues("IdentityFile"));
+ final Host h2 = osc.lookup("orcz");
+ assertNotNull(h2);
+ assertEquals(new File(home, "foo/bar"), h2.getIdentityFile());
+ assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath() },
+ h2.getConfig().getValues("IdentityFile"));
+ }
+
+ @Test
+ public void testEqualsSign() throws Exception {
+ config("Host=orcz\n\tConnectionAttempts = 5\n\tUser=\t foobar\t\n");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals(5, h.getConnectionAttempts());
+ assertEquals("foobar", h.getUser());
+ }
+
+ @Test
+ public void testMissingArgument() throws Exception {
+ config("Host=orcz\n\tSendEnv\nIdentityFile\t\nForwardX11\n\tUser=\t foobar\t\n");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals("foobar", h.getUser());
+ assertArrayEquals(new String[0], h.getConfig().getValues("SendEnv"));
+ assertNull(h.getIdentityFile());
+ assertNull(h.getConfig().getValue("ForwardX11"));
+ }
+
+ @Test
+ public void testHomeDirUserReplacement() throws Exception {
+ config("Host=orcz\n\tIdentityFile %d/.ssh/%u_id_dsa");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals(new File(new File(home, ".ssh"), "jex_junit_id_dsa"),
+ h.getIdentityFile());
+ }
+
+ @Test
+ public void testHostnameReplacement() throws Exception {
+ config("Host=orcz\nHost *.*\n\tHostname %h\nHost *\n\tHostname %h.example.org");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals("orcz.example.org", h.getHostName());
+ }
+
+ @Test
+ public void testRemoteUserReplacement() throws Exception {
+ config("Host=orcz\n\tUser foo\n" + "Host *.*\n\tHostname %h\n"
+ + "Host *\n\tHostname %h.ex%%20ample.org\n\tIdentityFile ~/.ssh/%h_%r_id_dsa");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals(
+ new File(new File(home, ".ssh"),
+ "orcz.ex%20ample.org_foo_id_dsa"),
+ h.getIdentityFile());
+ }
+
+ @Test
+ public void testLocalhostFQDNReplacement() throws Exception {
+ String localhost = SystemReader.getInstance().getHostname();
+ config("Host=orcz\n\tIdentityFile ~/.ssh/%l_id_dsa");
+ final Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ assertEquals(
+ new File(new File(home, ".ssh"), localhost + "_id_dsa"),
+ h.getIdentityFile());
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConfigTest.java
new file mode 100644
index 0000000..9610fbd
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConfigTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017, David Pursehouse <david.pursehouse@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.jgit.transport.PushConfig.PushRecurseSubmodulesMode;
+import org.junit.Test;
+
+public class PushConfigTest {
+ @Test
+ public void pushRecurseSubmoduleMatch() throws Exception {
+ assertTrue(PushRecurseSubmodulesMode.CHECK.matchConfigValue("check"));
+ assertTrue(PushRecurseSubmodulesMode.CHECK.matchConfigValue("CHECK"));
+
+ assertTrue(PushRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("on-demand"));
+ assertTrue(PushRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("ON-DEMAND"));
+ assertTrue(PushRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("on_demand"));
+ assertTrue(PushRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("ON_DEMAND"));
+
+ assertTrue(PushRecurseSubmodulesMode.NO.matchConfigValue("no"));
+ assertTrue(PushRecurseSubmodulesMode.NO.matchConfigValue("NO"));
+ assertTrue(PushRecurseSubmodulesMode.NO.matchConfigValue("false"));
+ assertTrue(PushRecurseSubmodulesMode.NO.matchConfigValue("FALSE"));
+ }
+
+ @Test
+ public void pushRecurseSubmoduleNoMatch() throws Exception {
+ assertFalse(PushRecurseSubmodulesMode.NO.matchConfigValue("N"));
+ assertFalse(PushRecurseSubmodulesMode.ON_DEMAND
+ .matchConfigValue("ONDEMAND"));
+ }
+
+ @Test
+ public void pushRecurseSubmoduleToConfigValue() {
+ assertEquals("on-demand",
+ PushRecurseSubmodulesMode.ON_DEMAND.toConfigValue());
+ assertEquals("check", PushRecurseSubmodulesMode.CHECK.toConfigValue());
+ assertEquals("false", PushRecurseSubmodulesMode.NO.toConfigValue());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
index abd2840..3836b7d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java
@@ -58,6 +58,8 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.Deflater;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -159,6 +161,45 @@ ReceivePack createReceivePack(final Repository db) {
}
@Test
+ public void resetsHaves() throws Exception {
+ AtomicReference<Set<ObjectId>> haves = new AtomicReference<>();
+ try (TransportLocal t = new TransportLocal(src, uriOf(dst),
+ dst.getDirectory()) {
+ @Override
+ ReceivePack createReceivePack(Repository db) {
+ dst.incrementOpen();
+
+ ReceivePack rp = super.createReceivePack(dst);
+ rp.setAdvertiseRefsHook(new AdvertiseRefsHook() {
+ @Override
+ public void advertiseRefs(BaseReceivePack rp2)
+ throws ServiceMayNotContinueException {
+ rp.setAdvertisedRefs(rp.getRepository().getAllRefs(),
+ null);
+ new HidePrivateHook().advertiseRefs(rp);
+ haves.set(rp.getAdvertisedObjects());
+ }
+
+ @Override
+ public void advertiseRefs(UploadPack uploadPack)
+ throws ServiceMayNotContinueException {
+ throw new UnsupportedOperationException();
+ }
+ });
+ return rp;
+ }
+ }) {
+ try (PushConnection c = t.openPush()) {
+ // Just has to open/close for advertisement.
+ }
+ }
+
+ assertEquals(1, haves.get().size());
+ assertTrue(haves.get().contains(B));
+ assertFalse(haves.get().contains(P));
+ }
+
+ @Test
public void testSuccess() throws Exception {
// Manually force a delta of an object so we reuse it later.
//
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java
index 0cada5c..a0cf0d2 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java
@@ -51,6 +51,7 @@
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -498,24 +499,48 @@ public void noPushInsteadOf() throws Exception {
}
@Test
- public void singlePushInsteadOf() throws Exception {
+ public void pushInsteadOfNotAppliedToPushUri() throws Exception {
config.setString("remote", "origin", "pushurl", "short:project.git");
config.setString("url", "https://server/repos/", "pushInsteadOf",
"short:");
RemoteConfig rc = new RemoteConfig(config, "origin");
assertFalse(rc.getPushURIs().isEmpty());
+ assertEquals("short:project.git",
+ rc.getPushURIs().get(0).toASCIIString());
+ }
+
+ @Test
+ public void pushInsteadOfAppliedToUri() throws Exception {
+ config.setString("remote", "origin", "url", "short:project.git");
+ config.setString("url", "https://server/repos/", "pushInsteadOf",
+ "short:");
+ RemoteConfig rc = new RemoteConfig(config, "origin");
+ assertFalse(rc.getPushURIs().isEmpty());
+ assertEquals("https://server/repos/project.git",
+ rc.getPushURIs().get(0).toASCIIString());
+ }
+
+ @Test
+ public void multiplePushInsteadOf() throws Exception {
+ config.setString("remote", "origin", "url", "prefixproject.git");
+ config.setStringList("url", "https://server/repos/", "pushInsteadOf",
+ Arrays.asList("pre", "prefix", "pref", "perf"));
+ RemoteConfig rc = new RemoteConfig(config, "origin");
+ assertFalse(rc.getPushURIs().isEmpty());
assertEquals("https://server/repos/project.git", rc.getPushURIs()
.get(0).toASCIIString());
}
@Test
- public void multiplePushInsteadOf() throws Exception {
- config.setString("remote", "origin", "pushurl", "prefixproject.git");
- config.setStringList("url", "https://server/repos/", "pushInsteadOf",
- Arrays.asList("pre", "prefix", "pref", "perf"));
+ public void pushInsteadOfNoPushUrl() throws Exception {
+ config.setString("remote", "origin", "url",
+ "http://git.eclipse.org/gitroot/jgit/jgit");
+ config.setStringList("url", "ssh://someone@git.eclipse.org:29418/",
+ "pushInsteadOf",
+ Collections.singletonList("http://git.eclipse.org/gitroot/"));
RemoteConfig rc = new RemoteConfig(config, "origin");
assertFalse(rc.getPushURIs().isEmpty());
- assertEquals("https://server/repos/project.git", rc.getPushURIs()
- .get(0).toASCIIString());
+ assertEquals("ssh://someone@git.eclipse.org:29418/jgit/jgit",
+ rc.getPushURIs().get(0).toASCIIString());
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java
new file mode 100644
index 0000000..27c7674
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java
@@ -0,0 +1,90 @@
+package org.eclipse.jgit.transport;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.UploadPack.RequestPolicy;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.eclipse.jgit.transport.resolver.UploadPackFactory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for server upload-pack utilities.
+ */
+public class UploadPackTest {
+ private URIish uri;
+
+ private TestProtocol<Object> testProtocol;
+
+ private Object ctx = new Object();
+
+ private InMemoryRepository server;
+
+ private InMemoryRepository client;
+
+ private RevCommit commit0;
+
+ private RevCommit commit1;
+
+ private RevCommit tip;
+
+ @Before
+ public void setUp() throws Exception {
+ server = newRepo("server");
+ client = newRepo("client");
+
+ TestRepository<InMemoryRepository> remote =
+ new TestRepository<>(server);
+ commit0 = remote.commit().message("0").create();
+ commit1 = remote.commit().message("1").parent(commit0).create();
+ tip = remote.commit().message("2").parent(commit1).create();
+ remote.update("master", tip);
+ }
+
+ @After
+ public void tearDown() {
+ Transport.unregister(testProtocol);
+ }
+
+ private static InMemoryRepository newRepo(String name) {
+ return new InMemoryRepository(new DfsRepositoryDescription(name));
+ }
+
+ @Test
+ public void testFetchParentOfShallowCommit() throws Exception {
+ testProtocol = new TestProtocol<>(
+ new UploadPackFactory<Object>() {
+ @Override
+ public UploadPack create(Object req, Repository db)
+ throws ServiceNotEnabledException,
+ ServiceNotAuthorizedException {
+ UploadPack up = new UploadPack(db);
+ up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT);
+ // assume client has a shallow commit
+ up.getRevWalk().assumeShallow(
+ Collections.singleton(commit1.getId()));
+ return up;
+ }
+ }, null);
+ uri = testProtocol.register(ctx, server);
+
+ assertFalse(client.hasObject(commit0.toObjectId()));
+
+ // Fetch of the parent of the shallow commit
+ try (Transport tn = testProtocol.open(uri, client, "server")) {
+ tn.fetch(NullProgressMonitor.INSTANCE,
+ Collections.singletonList(new RefSpec(commit0.name())));
+ assertTrue(client.hasObject(commit0.toObjectId()));
+ }
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java
index 7c819c5..0394f68 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java
@@ -42,6 +42,14 @@
*/
package org.eclipse.jgit.treewalk.filter;
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
@@ -53,14 +61,6 @@
import org.junit.Before;
import org.junit.Test;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-
public class PathFilterLogicTest extends RepositoryTestCase {
private ObjectId treeId;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java
index c6eca9d..d6ea8c6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java
@@ -44,6 +44,7 @@
package org.eclipse.jgit.util;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -186,6 +187,16 @@ public void testSet() {
}
@Test
+ public void testContains() {
+ IntList i = new IntList();
+ i.add(1);
+ i.add(4);
+ assertTrue(i.contains(1));
+ assertTrue(i.contains(4));
+ assertFalse(i.contains(2));
+ }
+
+ @Test
public void testToString() {
final IntList i = new IntList();
i.add(1);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/LongMapTest.java
similarity index 98%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/util/LongMapTest.java
index 1a86aaf..054c61e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/LongMapTest.java
@@ -41,7 +41,7 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.eclipse.jgit.transport;
+package org.eclipse.jgit.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java
index 7e11a61..d2d44ff 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java
@@ -90,6 +90,24 @@ public void testDecodeUInt16() {
}
@Test
+ public void testDecodeUInt24() {
+ assertEquals(0, NB.decodeUInt24(b(0, 0, 0), 0));
+ assertEquals(0, NB.decodeUInt24(padb(3, 0, 0, 0), 3));
+
+ assertEquals(3, NB.decodeUInt24(b(0, 0, 3), 0));
+ assertEquals(3, NB.decodeUInt24(padb(3, 0, 0, 3), 3));
+
+ assertEquals(0xcede03, NB.decodeUInt24(b(0xce, 0xde, 3), 0));
+ assertEquals(0xbade03, NB.decodeUInt24(padb(3, 0xba, 0xde, 3), 3));
+
+ assertEquals(0x03bade, NB.decodeUInt24(b(3, 0xba, 0xde), 0));
+ assertEquals(0x03bade, NB.decodeUInt24(padb(3, 3, 0xba, 0xde), 3));
+
+ assertEquals(0xffffff, NB.decodeUInt24(b(0xff, 0xff, 0xff), 0));
+ assertEquals(0xffffff, NB.decodeUInt24(padb(3, 0xff, 0xff, 0xff), 3));
+ }
+
+ @Test
public void testDecodeInt32() {
assertEquals(0, NB.decodeInt32(b(0, 0, 0, 0), 0));
assertEquals(0, NB.decodeInt32(padb(3, 0, 0, 0, 0), 3));
@@ -198,6 +216,39 @@ public void testEncodeInt16() {
}
@Test
+ public void testEncodeInt24() {
+ byte[] out = new byte[16];
+
+ prepareOutput(out);
+ NB.encodeInt24(out, 0, 0);
+ assertOutput(b(0, 0, 0), out, 0);
+
+ prepareOutput(out);
+ NB.encodeInt24(out, 3, 0);
+ assertOutput(b(0, 0, 0), out, 3);
+
+ prepareOutput(out);
+ NB.encodeInt24(out, 0, 3);
+ assertOutput(b(0, 0, 3), out, 0);
+
+ prepareOutput(out);
+ NB.encodeInt24(out, 3, 3);
+ assertOutput(b(0, 0, 3), out, 3);
+
+ prepareOutput(out);
+ NB.encodeInt24(out, 0, 0xc0deac);
+ assertOutput(b(0xc0, 0xde, 0xac), out, 0);
+
+ prepareOutput(out);
+ NB.encodeInt24(out, 3, 0xbadeac);
+ assertOutput(b(0xba, 0xde, 0xac), out, 3);
+
+ prepareOutput(out);
+ NB.encodeInt24(out, 3, -1);
+ assertOutput(b(0xff, 0xff, 0xff), out, 3);
+ }
+
+ @Test
public void testEncodeInt32() {
final byte[] out = new byte[16];
@@ -315,10 +366,24 @@ private static void assertOutput(final byte[] expect, final byte[] buf,
return r;
}
+ private static byte[] b(int a, int b, int c) {
+ return new byte[] { (byte) a, (byte) b, (byte) c };
+ }
+
private static byte[] b(final int a, final int b, final int c, final int d) {
return new byte[] { (byte) a, (byte) b, (byte) c, (byte) d };
}
+ private static byte[] padb(int len, int a, int b, int c) {
+ final byte[] r = new byte[len + 4];
+ for (int i = 0; i < len; i++)
+ r[i] = (byte) 0xaf;
+ r[len] = (byte) a;
+ r[len + 1] = (byte) b;
+ r[len + 2] = (byte) c;
+ return r;
+ }
+
private static byte[] padb(final int len, final int a, final int b,
final int c, final int d) {
final byte[] r = new byte[len + 4];
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java
index 5939714..6efdce6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java
@@ -43,7 +43,7 @@
package org.eclipse.jgit.util;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNotNull;
import java.io.UnsupportedEncodingException;
@@ -55,52 +55,51 @@ public class RawParseUtils_LineMapTest {
public void testEmpty() {
final IntList map = RawParseUtils.lineMap(new byte[] {}, 0, 0);
assertNotNull(map);
- assertEquals(2, map.size());
- assertEquals(Integer.MIN_VALUE, map.get(0));
- assertEquals(0, map.get(1));
+ assertArrayEquals(new int[]{Integer.MIN_VALUE, 0}, asInts(map));
}
@Test
public void testOneBlankLine() {
final IntList map = RawParseUtils.lineMap(new byte[] { '\n' }, 0, 1);
- assertEquals(3, map.size());
- assertEquals(Integer.MIN_VALUE, map.get(0));
- assertEquals(0, map.get(1));
- assertEquals(1, map.get(2));
+ assertArrayEquals(new int[]{Integer.MIN_VALUE, 0, 1}, asInts(map));
}
@Test
public void testTwoLineFooBar() throws UnsupportedEncodingException {
final byte[] buf = "foo\nbar\n".getBytes("ISO-8859-1");
final IntList map = RawParseUtils.lineMap(buf, 0, buf.length);
- assertEquals(4, map.size());
- assertEquals(Integer.MIN_VALUE, map.get(0));
- assertEquals(0, map.get(1));
- assertEquals(4, map.get(2));
- assertEquals(buf.length, map.get(3));
+ assertArrayEquals(new int[]{Integer.MIN_VALUE, 0, 4, buf.length}, asInts(map));
}
@Test
public void testTwoLineNoLF() throws UnsupportedEncodingException {
final byte[] buf = "foo\nbar".getBytes("ISO-8859-1");
final IntList map = RawParseUtils.lineMap(buf, 0, buf.length);
- assertEquals(4, map.size());
- assertEquals(Integer.MIN_VALUE, map.get(0));
- assertEquals(0, map.get(1));
- assertEquals(4, map.get(2));
- assertEquals(buf.length, map.get(3));
+ assertArrayEquals(new int[]{Integer.MIN_VALUE, 0, 4, buf.length}, asInts(map));
+ }
+
+ @Test
+ public void testBinary() throws UnsupportedEncodingException {
+ final byte[] buf = "xxxfoo\nb\0ar".getBytes("ISO-8859-1");
+ final IntList map = RawParseUtils.lineMap(buf, 3, buf.length);
+ assertArrayEquals(new int[]{Integer.MIN_VALUE, 3, buf.length}, asInts(map));
}
@Test
public void testFourLineBlanks() throws UnsupportedEncodingException {
final byte[] buf = "foo\n\n\nbar\n".getBytes("ISO-8859-1");
final IntList map = RawParseUtils.lineMap(buf, 0, buf.length);
- assertEquals(6, map.size());
- assertEquals(Integer.MIN_VALUE, map.get(0));
- assertEquals(0, map.get(1));
- assertEquals(4, map.get(2));
- assertEquals(5, map.get(3));
- assertEquals(6, map.get(4));
- assertEquals(buf.length, map.get(5));
+
+ assertArrayEquals(new int[]{
+ Integer.MIN_VALUE, 0, 4, 5, 6, buf.length
+ }, asInts(map));
+ }
+
+ private int[] asInts(IntList l) {
+ int[] result = new int[l.size()];
+ for (int i = 0; i < l.size(); i++) {
+ result[i] = l.get(i);
+ }
+ return result;
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java
index 76687d1..fb76ec4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java
@@ -128,7 +128,14 @@ public void testFormatYearsMonths() {
assertFormat(380, DAY_IN_MILLIS, "1 year, 1 month ago");
assertFormat(410, DAY_IN_MILLIS, "1 year, 2 months ago");
assertFormat(2, YEAR_IN_MILLIS, "2 years ago");
- assertFormat(1824, DAY_IN_MILLIS, "4 years, 12 months ago");
+ }
+
+ @Test
+ public void testFullYearMissingSomeDays() {
+ // avoid "x year(s), 12 months", as humans would always round this up to
+ // "x+1 years"
+ assertFormat(5 * 365 + 1, DAY_IN_MILLIS, "5 years ago");
+ assertFormat(2 * 365 - 10, DAY_IN_MILLIS, "2 years ago");
}
@Test
diff --git a/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit.ui/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit.ui/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.ui/.settings/org.eclipse.pde.api.tools.prefs
index cd148d9..c0030de 100644
--- a/org.eclipse.jgit.ui/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit.ui/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,4 +1,4 @@
-#Tue Oct 18 00:52:01 CEST 2011
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -8,6 +8,10 @@
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -47,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -58,6 +63,7 @@
INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
INVALID_JAVADOC_TAG=Ignore
INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
LEAK_EXTEND=Warning
@@ -75,6 +81,7 @@
METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
@@ -83,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
index 4312cc3..f810bf7 100644
--- a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
@@ -3,14 +3,14 @@
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit.ui
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-Vendor: %provider_name
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Export-Package: org.eclipse.jgit.awtui;version="4.8.1"
-Import-Package: org.eclipse.jgit.errors;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.lib;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.nls;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.revplot;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.revwalk;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.transport;version="[4.8.1,4.9.0)",
- org.eclipse.jgit.util;version="[4.8.1,4.9.0)"
+Export-Package: org.eclipse.jgit.awtui;version="4.9.9"
+Import-Package: org.eclipse.jgit.errors;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.lib;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.nls;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.revplot;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.revwalk;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.transport;version="[4.9.9,4.10.0)",
+ org.eclipse.jgit.util;version="[4.9.9,4.10.0)"
diff --git a/org.eclipse.jgit.ui/pom.xml b/org.eclipse.jgit.ui/pom.xml
index 06e45ef..45997a2 100644
--- a/org.eclipse.jgit.ui/pom.xml
+++ b/org.eclipse.jgit.ui/pom.xml
@@ -52,7 +52,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.ui</artifactId>
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 18ae9ea..379d76f 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -3,8 +3,8 @@
<resource path="META-INF/MANIFEST.MF">
<filter id="924844039">
<message_arguments>
- <message_argument value="4.8.1"/>
- <message_argument value="4.8.0"/>
+ <message_argument value="4.9.8"/>
+ <message_argument value="4.9.0"/>
</message_arguments>
</filter>
</resource>
@@ -18,16 +18,16 @@
<filter id="1141899266">
<message_arguments>
<message_argument value="4.5"/>
- <message_argument value="4.8"/>
+ <message_argument value="4.9"/>
<message_argument value="CONFIG_KEY_SUPPORTSATOMICFILECREATION"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/lib/Constants.java" type="org.eclipse.jgit.lib.Constants">
- <filter comment="LOCK_SUFFIX was backported to 4.7.3" id="1141899266">
+ <filter id="1141899266">
<message_arguments>
<message_argument value="4.7"/>
- <message_argument value="4.8"/>
+ <message_argument value="4.9"/>
<message_argument value="LOCK_SUFFIX"/>
</message_arguments>
</filter>
@@ -48,25 +48,34 @@
</message_arguments>
</filter>
</resource>
+ <resource path="src/org/eclipse/jgit/merge/ResolveMerger.java" type="org.eclipse.jgit.merge.ResolveMerger">
+ <filter id="1141899266">
+ <message_arguments>
+ <message_argument value="3.5"/>
+ <message_argument value="4.9"/>
+ <message_argument value="processEntry(CanonicalTreeParser, CanonicalTreeParser, CanonicalTreeParser, DirCacheBuildIterator, WorkingTreeIterator, boolean)"/>
+ </message_arguments>
+ </filter>
+ </resource>
<resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS">
<filter id="1141899266">
<message_arguments>
<message_argument value="4.5"/>
- <message_argument value="4.8"/>
+ <message_argument value="4.9"/>
<message_argument value="createNewFile(File)"/>
</message_arguments>
</filter>
<filter id="1141899266">
<message_arguments>
<message_argument value="4.5"/>
- <message_argument value="4.8"/>
+ <message_argument value="4.9"/>
<message_argument value="supportsAtomicCreateNewFile()"/>
</message_arguments>
</filter>
<filter id="1141899266">
<message_arguments>
<message_argument value="4.7"/>
- <message_argument value="4.8"/>
+ <message_argument value="4.9"/>
<message_argument value="createNewFileAtomic(File)"/>
</message_arguments>
</filter>
@@ -81,7 +90,7 @@
<filter id="1141899266">
<message_arguments>
<message_argument value="4.7"/>
- <message_argument value="4.8"/>
+ <message_argument value="4.9"/>
<message_argument value="LockToken"/>
</message_arguments>
</filter>
diff --git a/org.eclipse.jgit/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit/.settings/org.eclipse.jdt.ui.prefs
index c336cce..fef3713 100644
--- a/org.eclipse.jgit/.settings/org.eclipse.jdt.ui.prefs
+++ b/org.eclipse.jgit/.settings/org.eclipse.jdt.ui.prefs
@@ -9,21 +9,23 @@
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8"?><templates/>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.insert_inferred_type_arguments=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
@@ -39,11 +41,12 @@
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
sp_cleanup.remove_trailing_whitespaces=true
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=false
-sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
@@ -52,8 +55,10 @@
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
diff --git a/org.eclipse.jgit/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit/.settings/org.eclipse.pde.api.tools.prefs
index cd148d9..c0030de 100644
--- a/org.eclipse.jgit/.settings/org.eclipse.pde.api.tools.prefs
+++ b/org.eclipse.jgit/.settings/org.eclipse.pde.api.tools.prefs
@@ -1,4 +1,4 @@
-#Tue Oct 18 00:52:01 CEST 2011
+ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error
ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
@@ -8,6 +8,10 @@
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_FIELD=Error
CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
@@ -47,6 +51,7 @@
ILLEGAL_INSTANTIATE=Warning
ILLEGAL_OVERRIDE=Warning
ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
@@ -58,6 +63,7 @@
INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Ignore
INVALID_JAVADOC_TAG=Ignore
INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error
LEAK_EXTEND=Warning
@@ -75,6 +81,7 @@
METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Warning
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
@@ -83,10 +90,13 @@
TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
UNUSED_PROBLEM_FILTERS=Warning
automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
eclipse.preferences.version=1
incompatible_api_component_version=Error
incompatible_api_component_version_include_major_without_breaking_change=Disabled
incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Ignore
invalid_since_tag_version=Error
malformed_since_tag=Error
missing_since_tag=Error
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 9b6a9f1..daf17e0 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -2,12 +2,12 @@
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit
-Bundle-Version: 4.8.1.qualifier
+Bundle-Version: 4.9.9.qualifier
Bundle-Localization: plugin
Bundle-Vendor: %provider_name
Bundle-ActivationPolicy: lazy
-Export-Package: org.eclipse.jgit.annotations;version="4.8.1",
- org.eclipse.jgit.api;version="4.8.1";
+Export-Package: org.eclipse.jgit.annotations;version="4.9.9",
+ org.eclipse.jgit.api;version="4.9.9";
uses:="org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.diff,
@@ -21,51 +21,52 @@
org.eclipse.jgit.submodule,
org.eclipse.jgit.transport,
org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="4.8.1",
- org.eclipse.jgit.blame;version="4.8.1";
+ org.eclipse.jgit.api.errors;version="4.9.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
+ org.eclipse.jgit.attributes;version="4.9.9",
+ org.eclipse.jgit.blame;version="4.9.9";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.diff",
- org.eclipse.jgit.diff;version="4.8.1";
+ org.eclipse.jgit.diff;version="4.9.9";
uses:="org.eclipse.jgit.patch,
org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.util",
- org.eclipse.jgit.dircache;version="4.8.1";
+ org.eclipse.jgit.dircache;version="4.9.9";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.util,
org.eclipse.jgit.events,
org.eclipse.jgit.attributes",
- org.eclipse.jgit.errors;version="4.8.1";
+ org.eclipse.jgit.errors;version="4.9.9";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.internal.storage.pack,
org.eclipse.jgit.transport,
org.eclipse.jgit.dircache",
- org.eclipse.jgit.events;version="4.8.1";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="4.8.1",
- org.eclipse.jgit.gitrepo;version="4.8.1";
+ org.eclipse.jgit.events;version="4.9.9";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.fnmatch;version="4.9.9",
+ org.eclipse.jgit.gitrepo;version="4.9.9";
uses:="org.eclipse.jgit.api,
org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.xml.sax.helpers,
org.xml.sax",
- org.eclipse.jgit.gitrepo.internal;version="4.8.1";x-internal:=true,
- org.eclipse.jgit.hooks;version="4.8.1";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="4.8.1",
- org.eclipse.jgit.ignore.internal;version="4.8.1";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="4.8.1";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.ketch;version="4.8.1";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.dfs;version="4.8.1";
+ org.eclipse.jgit.gitrepo.internal;version="4.9.9";x-internal:=true,
+ org.eclipse.jgit.hooks;version="4.9.9";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.ignore;version="4.9.9",
+ org.eclipse.jgit.ignore.internal;version="4.9.9";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal;version="4.9.9";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
+ org.eclipse.jgit.internal.fsck;version="4.9.9";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal.ketch;version="4.9.9";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.dfs;version="4.9.9";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.http.server,
org.eclipse.jgit.http.test,
org.eclipse.jgit.lfs.test",
- org.eclipse.jgit.internal.storage.file;version="4.8.1";
+ org.eclipse.jgit.internal.storage.file;version="4.9.9";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.junit,
org.eclipse.jgit.junit.http,
@@ -73,9 +74,11 @@
org.eclipse.jgit.lfs,
org.eclipse.jgit.pgm,
org.eclipse.jgit.pgm.test",
- org.eclipse.jgit.internal.storage.pack;version="4.8.1";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftree;version="4.8.1";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.lib;version="4.8.1";
+ org.eclipse.jgit.internal.storage.io;version="4.9.9";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.pack;version="4.9.9";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.reftable;version="4.9.9";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.internal.storage.reftree;version="4.9.9";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.lib;version="4.9.9";
uses:="org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.util,
@@ -85,33 +88,33 @@
org.eclipse.jgit.treewalk,
org.eclipse.jgit.transport,
org.eclipse.jgit.submodule",
- org.eclipse.jgit.lib.internal;version="4.8.1";x-internal:=true,
- org.eclipse.jgit.merge;version="4.8.1";
+ org.eclipse.jgit.lib.internal;version="4.9.9";x-internal:=true,
+ org.eclipse.jgit.merge;version="4.9.9";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.diff,
org.eclipse.jgit.dircache,
org.eclipse.jgit.api",
- org.eclipse.jgit.nls;version="4.8.1",
- org.eclipse.jgit.notes;version="4.8.1";
+ org.eclipse.jgit.nls;version="4.9.9",
+ org.eclipse.jgit.notes;version="4.9.9";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="4.8.1";
+ org.eclipse.jgit.patch;version="4.9.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
+ org.eclipse.jgit.revplot;version="4.9.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
+ org.eclipse.jgit.revwalk;version="4.9.9";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.diff,
org.eclipse.jgit.revwalk.filter",
- org.eclipse.jgit.revwalk.filter;version="4.8.1";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="4.8.1";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
- org.eclipse.jgit.transport;version="4.8.1";
+ org.eclipse.jgit.revwalk.filter;version="4.9.9";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.file;version="4.9.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.pack;version="4.9.9";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.submodule;version="4.9.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.transport;version="4.9.9";
uses:="org.eclipse.jgit.transport.resolver,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.internal.storage.pack,
@@ -123,24 +126,24 @@
org.eclipse.jgit.transport.http,
org.eclipse.jgit.errors,
org.eclipse.jgit.storage.pack",
- org.eclipse.jgit.transport.http;version="4.8.1";uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
- org.eclipse.jgit.treewalk;version="4.8.1";
+ org.eclipse.jgit.transport.http;version="4.9.9";uses:="javax.net.ssl",
+ org.eclipse.jgit.transport.resolver;version="4.9.9";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
+ org.eclipse.jgit.treewalk;version="4.9.9";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.attributes,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.util,
org.eclipse.jgit.dircache",
- org.eclipse.jgit.treewalk.filter;version="4.8.1";uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="4.8.1";
+ org.eclipse.jgit.treewalk.filter;version="4.9.9";uses:="org.eclipse.jgit.treewalk",
+ org.eclipse.jgit.util;version="4.9.9";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.transport.http,
org.eclipse.jgit.storage.file,
org.ietf.jgss",
- org.eclipse.jgit.util.io;version="4.8.1",
- org.eclipse.jgit.util.sha1;version="4.8.1",
- org.eclipse.jgit.util.time;version="4.8.1"
+ org.eclipse.jgit.util.io;version="4.9.9",
+ org.eclipse.jgit.util.sha1;version="4.9.9",
+ org.eclipse.jgit.util.time;version="4.9.9"
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
com.jcraft.jsch;version="[0.1.37,0.2.0)",
diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
index 63e6bbc..2cb4baa 100644
--- a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
Bundle-Name: org.eclipse.jgit - Sources
Bundle-SymbolicName: org.eclipse.jgit.source
Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 4.8.1.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="4.8.1.qualifier";roots="."
+Bundle-Version: 4.9.9.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit;version="4.9.9.qualifier";roots="."
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index 673f3ff..d81c9d9 100644
--- a/org.eclipse.jgit/pom.xml
+++ b/org.eclipse.jgit/pom.xml
@@ -53,7 +53,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit</artifactId>
@@ -206,8 +206,8 @@
<pluginManagement>
<plugins>
<plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>findbugs-maven-plugin</artifactId>
+ <groupId>com.github.hazendaz.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
<configuration>
<excludeFilterFile>findBugs/FindBugsExcludeFilter.xml</excludeFilterFile>
</configuration>
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index e0b5bbf..b02430b 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -63,7 +63,7 @@
cannotCreateHEAD=cannot create HEAD
cannotCreateIndexfile=Cannot create an index file with name {0}
cannotCreateTempDir=Cannot create a temp dir
-cannotDeleteCheckedOutBranch=Branch {0} is checked out and can not be deleted
+cannotDeleteCheckedOutBranch=Branch {0} is checked out and cannot be deleted
cannotDeleteFile=Cannot delete file: {0}
cannotDeleteObjectsPath=Cannot delete {0}/{1}: {2}
cannotDeleteStaleTrackingRef=Cannot delete stale tracking ref {0}
@@ -81,7 +81,7 @@
cannotLock=Cannot lock {0}. Ensure that no other process has an open file handle on the lock file {0}.lock, then you may delete the lock file and retry.
cannotLockPackIn=Cannot lock pack in {0}
cannotMatchOnEmptyString=Cannot match on empty string.
-cannotMkdirObjectPath=Cannot mkdir {0}/{1}: {2}
+cannotMkdirObjectPath=Cannot create directory {0}/{1}: {2}
cannotMoveIndexTo=Cannot move index to {0}
cannotMovePackTo=Cannot move pack to {0}
cannotOpenService=cannot open {0}
@@ -89,6 +89,7 @@
cannotParseGitURIish=Cannot parse Git URI-ish
cannotPullOnARepoWithState=Cannot pull into a repository with state: {0}
cannotRead=Cannot read {0}
+cannotReadBackDelta=Cannot read delta type {0}
cannotReadBlob=Cannot read blob {0}
cannotReadCommit=Cannot read commit {0}
cannotReadFile=Cannot read file {0}
@@ -121,6 +122,7 @@
closed=closed
closeLockTokenFailed=Closing LockToken ''{0}'' failed
collisionOn=Collision on {0}
+commandClosedStderrButDidntExit=Command {0} closed stderr stream but didn''t exit within timeout {1} seconds
commandRejectedByHook=Rejected by "{0}" hook.\n{1}
commandWasCalledInTheWrongState=Command {0} was called in the wrong state
commitAlreadyExists=exists {0}
@@ -211,12 +213,14 @@
createBranchFailedUnknownReason=Create branch failed for unknown reason
createBranchUnexpectedResult=Create branch returned unexpected result {0}
createNewFileFailed=Could not create new file {0}
+createRequiresZeroOldId=Create requires old ID to be zero
credentialPassword=Password
credentialUsername=Username
daemonAlreadyRunning=Daemon already running
daysAgo={0} days ago
deleteBranchUnexpectedResult=Delete branch returned unexpected result {0}
deleteFileFailed=Could not delete file {0}
+deleteRequiresZeroNewId=Delete requires new ID to be zero
deleteTagUnexpectedResult=Delete tag returned unexpected result {0}
deletingNotSupported=Deleting {0} not supported.
destinationIsNotAWildcard=Destination is not a wildcard.
@@ -245,6 +249,7 @@
encryptionOnlyPBE=Encryption error: only password-based encryption (PBE) algorithms are supported.
endOfFileInEscape=End of file in escape
entryNotFoundByPath=Entry not found by path: {0}
+enumValueNotSupported0=Invalid value: {0}
enumValueNotSupported2=Invalid value: {0}.{1}={2}
enumValueNotSupported3=Invalid value: {0}.{1}.{2}={3}
enumValuesNotAvailable=Enumerated values of type {0} not available
@@ -260,6 +265,7 @@
exceptionCaughtDuringExecutionOfAddCommand=Exception caught during execution of add command
exceptionCaughtDuringExecutionOfArchiveCommand=Exception caught during execution of archive command
exceptionCaughtDuringExecutionOfCherryPickCommand=Exception caught during execution of cherry-pick command. {0}
+exceptionCaughtDuringExecutionOfCommand=Exception caught during execution of command ''{0}'' in ''{1}'', return code ''{2}'', error message ''{3}''
exceptionCaughtDuringExecutionOfCommitCommand=Exception caught during execution of commit command
exceptionCaughtDuringExecutionOfFetchCommand=Exception caught during execution of fetch command
exceptionCaughtDuringExecutionOfLsRemoteCommand=Exception caught during execution of ls-remote command
@@ -270,11 +276,10 @@
exceptionCaughtDuringExecutionOfRevertCommand=Exception caught during execution of revert command. {0}
exceptionCaughtDuringExecutionOfRmCommand=Exception caught during execution of rm command
exceptionCaughtDuringExecutionOfTagCommand=Exception caught during execution of tag command
-exceptionCaughtDuringExcecutionOfCommand=Exception caught during execution of command ''{0}'' in ''{1}'', return code ''{2}'', error message ''{3}''
exceptionHookExecutionInterrupted=Execution of "{0}" hook interrupted.
exceptionOccurredDuringAddingOfOptionToALogCommand=Exception occurred during adding of {0} as option to a Log command
exceptionOccurredDuringReadingOfGIT_DIR=Exception occurred during reading of $GIT_DIR/{0}. {1}
-exceptionWhileReadingPack=ERROR: Exception caught while accessing pack file {0}, the pack file might be corrupt, {1}. Caught {2} consecutive errors while trying to read this pack.
+exceptionWhileReadingPack=Exception caught while accessing pack file {0}, the pack file might be corrupt. Caught {1} consecutive errors while trying to read this pack.
expectedACKNAKFoundEOF=Expected ACK/NAK, found EOF
expectedACKNAKGot=Expected ACK/NAK, got: {0}
expectedBooleanStringValue=Expected boolean string value
@@ -308,6 +313,8 @@
gitmodulesNotFound=.gitmodules not found in tree.
headRequiredToStash=HEAD required to stash local changes
hoursAgo={0} hours ago
+httpConfigCannotNormalizeURL=Cannot normalize URL path {0}: too many .. segments
+httpConfigInvalidURL=Cannot parse URL from subsection http.{0} in git config; ignored.
hugeIndexesAreNotSupportedByJgitYet=Huge indexes are not supported by jgit, yet
hunkBelongsToAnotherFile=Hunk belongs to another file
hunkDisconnectedFromFile=Hunk disconnected from file
@@ -368,12 +375,17 @@
invalidPathPeriodAtEndWindows=Invalid path (period at end is ignored by Windows): {0}
invalidPathSpaceAtEndWindows=Invalid path (space at end is ignored by Windows): {0}
invalidPathReservedOnWindows=Invalid path (''{0}'' is reserved on Windows): {1}
+invalidRedirectLocation=Invalid redirect location {0} -> {1}
invalidReflogRevision=Invalid reflog revision: {0}
invalidRefName=Invalid ref name: {0}
+invalidReftableBlock=Invalid reftable block
+invalidReftableCRC=Invalid reftable CRC-32
+invalidReftableFile=Invalid reftable file
invalidRemote=Invalid remote: {0}
invalidRepositoryStateNoHead=Invalid repository --- cannot read HEAD
invalidShallowObject=invalid shallow object {0}, expected commit
invalidStageForPath=Invalid stage {0} for path {1}
+invalidSystemProperty=Invalid system property ''{0}'': ''{1}''; using default value {2}
invalidTagOption=Invalid tag option: {0}
invalidTimeout=Invalid timeout: {0}
invalidTimeUnitValue2=Invalid time unit value: {0}.{1}={2}
@@ -412,8 +424,11 @@
mergeRecursiveTooManyMergeBasesFor = "More than {0} merge bases for:\n a {1}\n b {2} found:\n count {3}"
messageAndTaggerNotAllowedInUnannotatedTags = Unannotated tags cannot have a message or tagger
minutesAgo={0} minutes ago
+mismatchOffset=mismatch offset for object {0}
+mismatchCRC=mismatch CRC for object {0}
missingAccesskey=Missing accesskey.
missingConfigurationForKey=No value for key {0} found in configuration
+missingCRC=missing CRC for object {0}
missingDeltaBase=delta base
missingForwardImageInGITBinaryPatch=Missing forward-image in GIT binary patch
missingObject=Missing {0} {1}
@@ -431,6 +446,7 @@
needPackOut=need packOut
needsAtLeastOneEntry=Needs at least one entry
needsWorkdir=Needs workdir
+newIdMustNotBeNull=New ID must not be null
newlineInQuotesNotAllowed=Newline in quotes not allowed
noApplyInDelete=No apply in delete
noClosingBracket=No closing {0} found for {1} at index {2}.
@@ -464,6 +480,7 @@
objectNotFoundIn=Object {0} not found in {1}.
obtainingCommitsForCherryPick=Obtaining commits that need to be cherry-picked
offsetWrittenDeltaBaseForObjectNotFoundInAPack=Offset-written delta base for object not found in a pack
+oldIdMustNotBeNull=Expected old ID must not be null
onlyAlreadyUpToDateAndFastForwardMergesAreAvailable=only already-up-to-date and fast forward merges are available
onlyOneFetchSupported=Only one fetch supported
onlyOneOperationCallPerConnectionIsSupported=Only one operation call per connection is supported.
@@ -471,7 +488,8 @@
openingConnection=Opening connection
operationCanceled=Operation {0} was canceled
outputHasAlreadyBeenStarted=Output has already been started.
-packChecksumMismatch=Pack checksum mismatch detected for pack file {0}
+overflowedReftableBlock=Overflowed reftable block
+packChecksumMismatch=Pack checksum mismatch detected for pack file {0}: .pack has {1} whilst .idx has {2}
packCorruptedWhileWritingToFilesystem=Pack corrupted while writing to filesystem
packDoesNotMatchIndex=Pack {0} does not match index
packedRefsHandleIsStale=packed-refs handle is stale, {0}. retry
@@ -498,6 +516,7 @@
pathIsNotInWorkingDir=Path is not in working dir
pathNotConfigured=Submodule path is not configured
peeledLineBeforeRef=Peeled line before ref.
+peeledRefIsRequired=Peeled ref is required.
peerDidNotSupplyACompleteObjectGraph=peer did not supply a complete object graph
personIdentEmailNonNull=E-mail address of PersonIdent must not be null.
personIdentNameNonNull=Name of PersonIdent must not be null.
@@ -526,10 +545,15 @@
receivePackInvalidLimit=Illegal limit parameter value {0}
receivePackTooLarge=Pack exceeds the limit of {0} bytes, rejecting the pack
receivingObjects=Receiving objects
+redirectBlocked=Redirection blocked: redirect {0} -> {1} not allowed
+redirectHttp=URI ''{0}'': following HTTP redirect #{1} {2} -> {3}
+redirectLimitExceeded=Redirected more than {0} times; aborted at {1} -> {2}
+redirectLocationMissing=Invalid redirect: no redirect location for {0}
+redirectsOff=Cannot redirect because http.followRedirects is false (HTTP status {0})
refAlreadyExists=already exists
refAlreadyExists1=Ref {0} already exists
reflogEntryNotFound=Entry {0} not found in reflog for ''{1}''
-refNotResolved=Ref {0} can not be resolved
+refNotResolved=Ref {0} cannot be resolved
refUpdateReturnCodeWas=RefUpdate return code was: {0}
remoteConfigHasNoURIAssociated=Remote config "{0}" has no URIs associated
remoteDoesNotHaveSpec=Remote does not have {0} available for fetch.
@@ -573,7 +597,7 @@
selectingCommits=Selecting commits
sequenceTooLargeForDiffAlgorithm=Sequence too large for difference algorithm.
serviceNotEnabledNoName=Service not enabled
-serviceNotPermitted={0} not permitted
+serviceNotPermitted={1} not permitted on ''{0}''
sha1CollisionDetected1=SHA-1 collision detected on {0}
shallowCommitsAlreadyInitialized=Shallow commits have already been initialized
shallowPacksRequireDepthWalk=Shallow packs require a DepthWalk
@@ -591,6 +615,15 @@
sourceRefDoesntResolveToAnyObject=Source ref {0} doesn''t resolve to any object.
sourceRefNotSpecifiedForRefspec=Source ref not specified for refspec: {0}
squashCommitNotUpdatingHEAD=Squash commit -- not updating HEAD
+sshUserNameError=Jsch error: failed to set SSH user name correctly to ''{0}''; using ''{1}'' picked up from SSH config file.
+sslFailureExceptionMessage=Secure connection to {0} could not be stablished because of SSL problems
+sslFailureInfo=A secure connection to {0}\ncould not be established because the server''s certificate could not be validated.
+sslFailureCause=SSL reported: {0}
+sslFailureTrustExplanation=Do you want to skip SSL verification for this server?
+sslTrustAlways=Always skip SSL verification for this server from now on
+sslTrustForRepo=Skip SSL verification for git operations for repository {0}
+sslTrustNow=Skip SSL verification for this single git operation
+sslVerifyCannotSave=Could not save setting for http.sslVerify
staleRevFlagsOn=Stale RevFlags on {0}
startingReadStageWithoutWrittenRequestDataPendingIsNotSupported=Starting read stage without written request data pending is not supported
stashApplyConflict=Applying stashed changes resulted in a conflict
@@ -602,6 +635,7 @@
stashDropDeleteRefFailed=Deleting stash reference failed with result: {0}
stashDropFailed=Dropping stashed commit failed
stashDropMissingReflog=Stash reflog does not contain entry ''{0}''
+stashDropNotSupported=Dropping stash not supported on this ref backend
stashFailed=Stashing local changes did not successfully complete
stashResolveFailed=Reference ''{0}'' does not resolve to stashed commit
statelessRPCRequiresOptionToBeEnabled=stateless RPC requires {0} to be enabled
@@ -621,6 +655,7 @@
tagNameInvalid=tag name {0} is invalid
tagOnRepoWithoutHEADCurrentlyNotSupported=Tag on repository without HEAD currently not supported
theFactoryMustNotBeNull=The factory must not be null
+threadInterruptedWhileRunning="Current thread interrupted while running {0}"
timeIsUncertain=Time is uncertain
timerAlreadyTerminated=Timer already terminated
tooManyCommands=Too many commands
@@ -654,14 +689,17 @@
tSizeMustBeGreaterOrEqual1=tSize must be >= 1
unableToCheckConnectivity=Unable to check connectivity.
unableToCreateNewObject=Unable to create new object: {0}
+unableToReadPackfile=Unable to read packfile {0}
unableToRemovePath=Unable to remove path ''{0}''
unableToStore=Unable to store {0}.
unableToWrite=Unable to write {0}
unauthorized=Unauthorized
+underflowedReftableBlock=Underflowed reftable block
unencodeableFile=Unencodable file: {0}
unexpectedCompareResult=Unexpected metadata comparison result: {0}
unexpectedEndOfConfigFile=Unexpected end of config file
unexpectedEndOfInput=Unexpected end of input
+unexpectedEofInPack=Unexpected EOF in partially created pack
unexpectedHunkTrailer=Unexpected hunk trailer
unexpectedOddResult=odd: {0} + {1} - {2}
unexpectedRefReport={0}: unexpected ref report: {1}
@@ -672,6 +710,7 @@
unknownHost=unknown host
unknownIndexVersionOrCorruptIndex=Unknown index version (or corrupt index): {0}
unknownObject=unknown object
+unknownObjectInIndex=unknown object {0} found in index but not in pack file
unknownObjectType=Unknown object type {0}.
unknownObjectType2=unknown
unknownRepositoryFormat=Unknown repository format
@@ -694,7 +733,9 @@
unsupportedOperationNotAddAtEnd=Not add-at-end: {0}
unsupportedPackIndexVersion=Unsupported pack index version {0}
unsupportedPackVersion=Unsupported pack version {0}.
+unsupportedReftableVersion=Unsupported reftable version {0}.
unsupportedRepositoryDescription=Repository description not supported
+updateRequiresOldIdAndNewId=Update requires both old ID and new ID to be nonzero
updatingHeadFailed=Updating HEAD failed
updatingReferences=Updating references
updatingRefFailed=Updating the ref {0} to {1} failed. ReturnCode from RefUpdate.update() was {2}
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/storage/dfs/DfsText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/storage/dfs/DfsText.properties
index 4bbc4cc..2c4bd06 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/storage/dfs/DfsText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/storage/dfs/DfsText.properties
@@ -1,6 +1,4 @@
cannotReadIndex=Cannot read index {0}
-cannotReadBackDelta=Cannot read delta type {0}
shortReadOfBlock=Short read of block at {0} in pack {1}; expected {2} bytes, received only {3}
shortReadOfIndex=Short read of index {0}
-unexpectedEofInPack=Unexpected EOF in partially created pack
willNotStoreEmptyPack=Cannot store empty pack
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
index 21d6283..6b20da3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -47,8 +47,10 @@
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.EnumSet;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
import org.eclipse.jgit.api.CheckoutResult.Status;
import org.eclipse.jgit.api.errors.CheckoutConflictException;
@@ -66,6 +68,7 @@
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.UnmergedPathException;
+import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
@@ -175,6 +178,8 @@ private Stage(int number) {
private boolean checkoutAllPaths;
+ private Set<String> actuallyModifiedPaths;
+
/**
* @param repo
*/
@@ -410,7 +415,8 @@ public CheckoutCommand setAllPaths(boolean all) {
}
/**
- * Checkout paths into index and working directory
+ * Checkout paths into index and working directory, firing a
+ * {@link WorkingTreeModifiedEvent} if the working tree was modified.
*
* @return this instance
* @throws IOException
@@ -418,6 +424,7 @@ public CheckoutCommand setAllPaths(boolean all) {
*/
protected CheckoutCommand checkoutPaths() throws IOException,
RefNotFoundException {
+ actuallyModifiedPaths = new HashSet<>();
DirCache dc = repo.lockDirCache();
try (RevWalk revWalk = new RevWalk(repo);
TreeWalk treeWalk = new TreeWalk(repo,
@@ -432,7 +439,16 @@ protected CheckoutCommand checkoutPaths() throws IOException,
checkoutPathsFromCommit(treeWalk, dc, commit);
}
} finally {
- dc.unlock();
+ try {
+ dc.unlock();
+ } finally {
+ WorkingTreeModifiedEvent event = new WorkingTreeModifiedEvent(
+ actuallyModifiedPaths, null);
+ actuallyModifiedPaths = null;
+ if (!event.isEmpty()) {
+ repo.fireEvent(event);
+ }
+ }
}
return this;
}
@@ -461,9 +477,11 @@ public void apply(DirCacheEntry ent) {
int stage = ent.getStage();
if (stage > DirCacheEntry.STAGE_0) {
if (checkoutStage != null) {
- if (stage == checkoutStage.number)
+ if (stage == checkoutStage.number) {
checkoutPath(ent, r, new CheckoutMetadata(
eolStreamType, filterCommand));
+ actuallyModifiedPaths.add(path);
+ }
} else {
UnmergedPathException e = new UnmergedPathException(
ent);
@@ -472,6 +490,7 @@ public void apply(DirCacheEntry ent) {
} else {
checkoutPath(ent, r, new CheckoutMetadata(eolStreamType,
filterCommand));
+ actuallyModifiedPaths.add(path);
}
}
});
@@ -492,13 +511,15 @@ private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc,
final EolStreamType eolStreamType = treeWalk.getEolStreamType();
final String filterCommand = treeWalk
.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE);
- editor.add(new PathEdit(treeWalk.getPathString()) {
+ final String path = treeWalk.getPathString();
+ editor.add(new PathEdit(path) {
@Override
public void apply(DirCacheEntry ent) {
ent.setObjectId(blobId);
ent.setFileMode(mode);
checkoutPath(ent, r,
new CheckoutMetadata(eolStreamType, filterCommand));
+ actuallyModifiedPaths.add(path);
}
});
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
index c58efb1..e41a03b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
@@ -54,6 +54,7 @@
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.NoWorkTreeException;
+import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
@@ -135,6 +136,10 @@ else if (fs.isDirectory(f))
}
} catch (IOException e) {
throw new JGitInternalException(e.getMessage(), e);
+ } finally {
+ if (!files.isEmpty()) {
+ repo.fireEvent(new WorkingTreeModifiedEvent(null, files));
+ }
}
return files;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
index d450c64..bde8e63 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -50,6 +50,7 @@
import java.util.Collection;
import java.util.List;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.JGitInternalException;
@@ -157,6 +158,16 @@ public CloneCommand() {
}
/**
+ * Get the git directory. This is primarily used for tests.
+ *
+ * @return the git directory
+ */
+ @Nullable
+ File getDirectory() {
+ return directory;
+ }
+
+ /**
* Executes the {@code Clone} command.
*
* The Git instance returned by this command needs to be closed by the
@@ -232,9 +243,9 @@ private static boolean isNonEmptyDirectory(File dir) {
return false;
}
- private void verifyDirectories(URIish u) {
+ void verifyDirectories(URIish u) {
if (directory == null && gitDir == null) {
- directory = new File(u.getHumanishName(), Constants.DOT_GIT);
+ directory = new File(u.getHumanishName() + (bare ? Constants.DOT_GIT_EXT : "")); //$NON-NLS-1$
}
directoryExistsInitially = directory != null && directory.exists();
gitDirExistsInitially = gitDir != null && gitDir.exists();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
index 274ece6..e29fc05 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -482,7 +482,7 @@ private DirCache createTemporaryIndex(ObjectId headId, DirCache index,
JGitText.get().entryNotFoundByPath, only.get(i)));
// there must be at least one change
- if (emptyCommit)
+ if (emptyCommit && !allowEmpty.booleanValue())
// Would like to throw a EmptyCommitException. But this would break the API
// TODO(ch): Change this in the next release
throw new JGitInternalException(JGitText.get().emptyCommit);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
index 389c511..68b1bd9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
@@ -47,17 +47,22 @@
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.InvalidPatternException;
import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.ignore.internal.IMatcher;
+import org.eclipse.jgit.ignore.internal.PathMatcher;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@@ -94,6 +99,11 @@ public class DescribeCommand extends GitCommand<String> {
private boolean longDesc;
/**
+ * Pattern matchers to be applied to tags under consideration
+ */
+ private List<IMatcher> matchers = new ArrayList<>();
+
+ /**
*
* @param repo
*/
@@ -170,6 +180,55 @@ private String longDescription(Ref tag, int depth, ObjectId tip)
}
/**
+ * Sets one or more {@code glob(7)} patterns that tags must match to be considered.
+ * If multiple patterns are provided, tags only need match one of them.
+ *
+ * @param patterns the {@code glob(7)} pattern or patterns
+ * @return {@code this}
+ * @throws InvalidPatternException if the pattern passed in was invalid.
+ *
+ * @see <a
+ * href="https://www.kernel.org/pub/software/scm/git/docs/git-describe.html"
+ * >Git documentation about describe</a>
+ * @since 4.9
+ */
+ public DescribeCommand setMatch(String... patterns) throws InvalidPatternException {
+ for (String p : patterns) {
+ matchers.add(PathMatcher.createPathMatcher(p, null, false));
+ }
+ return this;
+ }
+
+ private Optional<Ref> getBestMatch(List<Ref> tags) {
+ if (tags == null || tags.size() == 0) {
+ return Optional.empty();
+ } else if (matchers.size() == 0) {
+ // No matchers, simply return the first tag entry
+ return Optional.of(tags.get(0));
+ } else {
+ // Find the first tag that matches one of the matchers; precedence according to matcher definition order
+ for (IMatcher matcher : matchers) {
+ Optional<Ref> match = tags.stream()
+ .filter(tag -> matcher.matches(tag.getName(), false,
+ false))
+ .findFirst();
+ if (match.isPresent()) {
+ return match;
+ }
+ }
+ return Optional.empty();
+ }
+ }
+
+ private ObjectId getObjectIdFromRef(Ref r) {
+ ObjectId key = repo.peel(r).getPeeledObjectId();
+ if (key == null) {
+ key = r.getObjectId();
+ }
+ return key;
+ }
+
+ /**
* Describes the specified commit. Target defaults to HEAD if no commit was
* set explicitly.
*
@@ -189,14 +248,9 @@ public String call() throws GitAPIException {
if (target == null)
setTarget(Constants.HEAD);
- Map<ObjectId, Ref> tags = new HashMap<>();
-
- for (Ref r : repo.getRefDatabase().getRefs(R_TAGS).values()) {
- ObjectId key = repo.peel(r).getPeeledObjectId();
- if (key == null)
- key = r.getObjectId();
- tags.put(key, r);
- }
+ Collection<Ref> tagList = repo.getRefDatabase().getRefs(R_TAGS).values();
+ Map<ObjectId, List<Ref>> tags = tagList.stream()
+ .collect(Collectors.groupingBy(this::getObjectIdFromRef));
// combined flags of all the candidate instances
final RevFlagSet allFlags = new RevFlagSet();
@@ -242,11 +296,11 @@ String describe(ObjectId tip) throws IOException {
}
List<Candidate> candidates = new ArrayList<>(); // all the candidates we find
- // is the target already pointing to a tag? if so, we are done!
- Ref lucky = tags.get(target);
- if (lucky != null) {
- return longDesc ? longDescription(lucky, 0, target) : lucky
- .getName().substring(R_TAGS.length());
+ // is the target already pointing to a suitable tag? if so, we are done!
+ Optional<Ref> bestMatch = getBestMatch(tags.get(target));
+ if (bestMatch.isPresent()) {
+ return longDesc ? longDescription(bestMatch.get(), 0, target) :
+ bestMatch.get().getName().substring(R_TAGS.length());
}
w.markStart(target);
@@ -258,9 +312,9 @@ String describe(ObjectId tip) throws IOException {
// if a tag already dominates this commit,
// then there's no point in picking a tag on this commit
// since the one that dominates it is always more preferable
- Ref t = tags.get(c);
- if (t != null) {
- Candidate cd = new Candidate(c, t);
+ bestMatch = getBestMatch(tags.get(c));
+ if (bestMatch.isPresent()) {
+ Candidate cd = new Candidate(c, bestMatch.get());
candidates.add(cd);
cd.depth = seen;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
index 785c20c..5270283 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
@@ -42,12 +42,16 @@
*/
package org.eclipse.jgit.api;
+import static java.util.stream.Collectors.toList;
+
import java.io.IOException;
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidConfigurationException;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
@@ -191,6 +195,7 @@ private void fetchSubmodules(FetchResult results)
.setThin(thin).setRefSpecs(refSpecs)
.setDryRun(dryRun)
.setRecurseSubmodules(recurseMode);
+ configure(f);
if (callback != null) {
callback.fetchingSubmodule(walk.getPath());
}
@@ -258,11 +263,19 @@ public FetchResult call() throws GitAPIException, InvalidRemoteException,
* Set the mode to be used for recursing into submodules.
*
* @param recurse
+ * corresponds to the
+ * --recurse-submodules/--no-recurse-submodules options. If
+ * {@code null} use the value of the
+ * {@code submodule.name.fetchRecurseSubmodules} option
+ * configured per submodule. If not specified there, use the
+ * value of the {@code fetch.recurseSubmodules} option configured
+ * in git config. If not configured in either, "on-demand" is the
+ * built-in default.
* @return {@code this}
* @since 4.7
*/
public FetchCommand setRecurseSubmodules(
- FetchRecurseSubmodulesMode recurse) {
+ @Nullable FetchRecurseSubmodulesMode recurse) {
checkCallable();
submoduleRecurseMode = recurse;
return this;
@@ -382,13 +395,21 @@ public List<RefSpec> getRefSpecs() {
*
* @param specs
* @return {@code this}
+ * @since 4.9
+ */
+ public FetchCommand setRefSpecs(String... specs) {
+ return setRefSpecs(
+ Arrays.stream(specs).map(RefSpec::new).collect(toList()));
+ }
+
+ /**
+ * The ref specs to be used in the fetch operation
+ *
+ * @param specs
+ * @return {@code this}
*/
public FetchCommand setRefSpecs(RefSpec... specs) {
- checkCallable();
- this.refSpecs.clear();
- for (RefSpec spec : specs)
- refSpecs.add(spec);
- return this;
+ return setRefSpecs(Arrays.asList(specs));
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
index b5d9e8a..75460fb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
@@ -53,6 +53,7 @@
import java.util.Locale;
import java.util.Map;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.MergeResult.MergeStatus;
import org.eclipse.jgit.api.errors.CheckoutConflictException;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
@@ -63,6 +64,7 @@
import org.eclipse.jgit.api.errors.NoMessageException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.dircache.DirCacheCheckout;
+import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Config.ConfigEnum;
@@ -354,6 +356,10 @@ public MergeResult call() throws GitAPIException, NoHeadException,
.getMergeResults();
failingPaths = resolveMerger.getFailingPaths();
unmergedPaths = resolveMerger.getUnmergedPaths();
+ if (!resolveMerger.getModifiedFiles().isEmpty()) {
+ repo.fireEvent(new WorkingTreeModifiedEvent(
+ resolveMerger.getModifiedFiles(), null));
+ }
} else
noProblems = merger.merge(headCommit, srcCommit);
refLogMessage.append(": Merge made by "); //$NON-NLS-1$
@@ -554,12 +560,15 @@ public MergeCommand setSquash(boolean squash) {
* Sets the fast forward mode.
*
* @param fastForwardMode
- * corresponds to the --ff/--no-ff/--ff-only options. --ff is the
- * default option.
+ * corresponds to the --ff/--no-ff/--ff-only options. If
+ * {@code null} use the value of the {@code merge.ff} option
+ * configured in git config. If this option is not configured
+ * --ff is the built-in default.
* @return {@code this}
* @since 2.2
*/
- public MergeCommand setFastForward(FastForwardMode fastForwardMode) {
+ public MergeCommand setFastForward(
+ @Nullable FastForwardMode fastForwardMode) {
checkCallable();
this.fastForwardMode = fastForwardMode;
return this;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
index 9c5ae43..aa97996 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
@@ -47,6 +47,9 @@
import java.io.IOException;
import java.text.MessageFormat;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
+import org.eclipse.jgit.api.MergeCommand.FastForwardMode.Merge;
import org.eclipse.jgit.api.RebaseCommand.Operation;
import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.api.errors.DetachedHeadException;
@@ -96,6 +99,8 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> {
private TagOpt tagOption;
+ private FastForwardMode fastForwardMode;
+
private FetchRecurseSubmodulesMode submoduleRecurseMode = null;
/**
@@ -347,10 +352,9 @@ public PullResult call() throws GitAPIException,
result = new PullResult(fetchRes, remote, rebaseRes);
} else {
MergeCommand merge = new MergeCommand(repo);
- merge.include(upstreamName, commitToMerge);
- merge.setStrategy(strategy);
- merge.setProgressMonitor(monitor);
- MergeResult mergeRes = merge.call();
+ MergeResult mergeRes = merge.include(upstreamName, commitToMerge)
+ .setStrategy(strategy).setProgressMonitor(monitor)
+ .setFastForward(getFastForwardMode()).call();
monitor.update(1);
result = new PullResult(fetchRes, remote, mergeRes);
}
@@ -433,14 +437,36 @@ public PullCommand setTagOpt(TagOpt tagOpt) {
}
/**
+ * Sets the fast forward mode. It is used if pull is configured to do a
+ * merge as opposed to rebase. If non-{@code null} takes precedence over the
+ * fast-forward mode configured in git config.
+ *
+ * @param fastForwardMode
+ * corresponds to the --ff/--no-ff/--ff-only options. If
+ * {@code null} use the value of {@code pull.ff} configured in
+ * git config. If {@code pull.ff} is not configured fall back to
+ * the value of {@code merge.ff}. If {@code merge.ff} is not
+ * configured --ff is the built-in default.
+ * @return {@code this}
+ * @since 4.9
+ */
+ public PullCommand setFastForward(
+ @Nullable FastForwardMode fastForwardMode) {
+ checkCallable();
+ this.fastForwardMode = fastForwardMode;
+ return this;
+ }
+
+ /**
* Set the mode to be used for recursing into submodules.
*
* @param recurse
* @return {@code this}
* @since 4.7
+ * @see FetchCommand#setRecurseSubmodules(FetchRecurseSubmodulesMode)
*/
public PullCommand setRecurseSubmodules(
- FetchRecurseSubmodulesMode recurse) {
+ @Nullable FetchRecurseSubmodulesMode recurse) {
this.submoduleRecurseMode = recurse;
return this;
}
@@ -470,4 +496,15 @@ public static BranchRebaseMode getRebaseMode(String branchName,
}
return mode;
}
+
+ private FastForwardMode getFastForwardMode() {
+ if (fastForwardMode != null) {
+ return fastForwardMode;
+ }
+ Config config = repo.getConfig();
+ Merge ffMode = config.getEnum(Merge.values(),
+ ConfigConstants.CONFIG_PULL_SECTION, null,
+ ConfigConstants.CONFIG_KEY_FF, null);
+ return ffMode != null ? FastForwardMode.valueOf(ffMode) : null;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index 850ff49..955c50b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -425,6 +425,7 @@ private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent,
refUpdate.setNewObjectId(commitId);
refUpdate.setRefLogIdent(refLogIdent);
refUpdate.setRefLogMessage(refLogMessage, false);
+ refUpdate.setForceRefLog(true);
if (currentRef != null)
refUpdate.setExpectedOldObjectId(currentRef.getObjectId());
else
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
index 04caa0f..394bea5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
@@ -109,4 +109,4 @@ public Collection<ReflogEntry> call() throws GitAPIException,
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java
index 9e2cf31..48c23f5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java
@@ -44,8 +44,10 @@
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
+import java.util.List;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
@@ -53,6 +55,7 @@
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuildIterator;
import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
@@ -145,6 +148,7 @@ public DirCache call() throws GitAPIException,
checkCallable();
DirCache dc = null;
+ List<String> actuallyDeletedFiles = new ArrayList<>();
try (final TreeWalk tw = new TreeWalk(repo)) {
dc = repo.lockDirCache();
DirCacheBuilder builder = dc.builder();
@@ -157,11 +161,14 @@ public DirCache call() throws GitAPIException,
if (!cached) {
final FileMode mode = tw.getFileMode(0);
if (mode.getObjectType() == Constants.OBJ_BLOB) {
+ String relativePath = tw.getPathString();
final File path = new File(repo.getWorkTree(),
- tw.getPathString());
+ relativePath);
// Deleting a blob is simply a matter of removing
// the file or symlink named by the tree entry.
- delete(path);
+ if (delete(path)) {
+ actuallyDeletedFiles.add(relativePath);
+ }
}
}
}
@@ -171,16 +178,28 @@ public DirCache call() throws GitAPIException,
throw new JGitInternalException(
JGitText.get().exceptionCaughtDuringExecutionOfRmCommand, e);
} finally {
- if (dc != null)
- dc.unlock();
+ try {
+ if (dc != null) {
+ dc.unlock();
+ }
+ } finally {
+ if (!actuallyDeletedFiles.isEmpty()) {
+ repo.fireEvent(new WorkingTreeModifiedEvent(null,
+ actuallyDeletedFiles));
+ }
+ }
}
return dc;
}
- private void delete(File p) {
- while (p != null && !p.equals(repo.getWorkTree()) && p.delete())
+ private boolean delete(File p) {
+ boolean deleted = false;
+ while (p != null && !p.equals(repo.getWorkTree()) && p.delete()) {
+ deleted = true;
p = p.getParentFile();
+ }
+ return deleted;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
index 10ec2a6..b56fb25 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012, GitHub Inc.
+ * Copyright (C) 2012, 2017 GitHub Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -44,6 +44,9 @@
import java.io.IOException;
import java.text.MessageFormat;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRefNameException;
@@ -58,6 +61,7 @@
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.CheckoutConflictException;
+import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
@@ -198,7 +202,13 @@ public ObjectId call() throws GitAPIException,
"stash" }); //$NON-NLS-1$
merger.setBase(stashHeadCommit);
merger.setWorkingTreeIterator(new FileTreeIterator(repo));
- if (merger.merge(headCommit, stashCommit)) {
+ boolean mergeSucceeded = merger.merge(headCommit, stashCommit);
+ List<String> modifiedByMerge = merger.getModifiedFiles();
+ if (!modifiedByMerge.isEmpty()) {
+ repo.fireEvent(
+ new WorkingTreeModifiedEvent(modifiedByMerge, null));
+ }
+ if (mergeSucceeded) {
DirCache dc = repo.lockDirCache();
DirCacheCheckout dco = new DirCacheCheckout(repo, headTree,
dc, merger.getResultTreeId());
@@ -329,6 +339,7 @@ private void resetIndex(RevTree tree) throws IOException {
private void resetUntracked(RevTree tree) throws CheckoutConflictException,
IOException {
+ Set<String> actuallyModifiedPaths = new HashSet<>();
// TODO maybe NameConflictTreeWalk ?
try (TreeWalk walk = new TreeWalk(repo)) {
walk.addTree(tree);
@@ -361,6 +372,12 @@ private void resetUntracked(RevTree tree) throws CheckoutConflictException,
checkoutPath(entry, reader,
new CheckoutMetadata(eolStreamType, null));
+ actuallyModifiedPaths.add(entry.getPathString());
+ }
+ } finally {
+ if (!actuallyModifiedPaths.isEmpty()) {
+ repo.fireEvent(new WorkingTreeModifiedEvent(
+ actuallyModifiedPaths, null));
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
index 681f8e6..77a7fff 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
@@ -62,6 +62,7 @@
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.UnmergedPathException;
+import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
@@ -211,6 +212,7 @@ private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent,
refUpdate.setNewObjectId(commitId);
refUpdate.setRefLogIdent(refLogIdent);
refUpdate.setRefLogMessage(refLogMessage, false);
+ refUpdate.setForceRefLog(true);
if (currentRef != null)
refUpdate.setExpectedOldObjectId(currentRef.getObjectId());
else
@@ -240,6 +242,7 @@ private Ref getHead() throws GitAPIException {
public RevCommit call() throws GitAPIException {
checkCallable();
+ List<String> deletedFiles = new ArrayList<>();
Ref head = getHead();
try (ObjectReader reader = repo.newObjectReader()) {
RevCommit headCommit = parseCommit(reader, head.getObjectId());
@@ -377,9 +380,11 @@ public void apply(DirCacheEntry ent) {
// Remove untracked files
if (includeUntracked) {
for (DirCacheEntry entry : untracked) {
+ String repoRelativePath = entry.getPathString();
File file = new File(repo.getWorkTree(),
- entry.getPathString());
+ repoRelativePath);
FileUtils.delete(file);
+ deletedFiles.add(repoRelativePath);
}
}
@@ -394,6 +399,11 @@ public void apply(DirCacheEntry ent) {
return parseCommit(reader, commitId);
} catch (IOException e) {
throw new JGitInternalException(JGitText.get().stashFailed, e);
+ } finally {
+ if (!deletedFiles.isEmpty()) {
+ repo.fireEvent(
+ new WorkingTreeModifiedEvent(null, deletedFiles));
+ }
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
index e215bdf..85e7b3d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
@@ -56,6 +56,7 @@
import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.RefDirectory;
import org.eclipse.jgit.internal.storage.file.ReflogWriter;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
@@ -68,6 +69,9 @@
/**
* Command class to delete a stashed commit reference
+ * <p>
+ * Currently only supported on a traditional file repository using
+ * one-file-per-ref reflogs.
*
* @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-stash.html"
* >Git documentation about Stash</a>
@@ -84,6 +88,10 @@ public class StashDropCommand extends GitCommand<ObjectId> {
*/
public StashDropCommand(Repository repo) {
super(repo);
+ if (!(repo.getRefDatabase() instanceof RefDirectory)) {
+ throw new UnsupportedOperationException(
+ JGitText.get().stashDropNotSupported);
+ }
}
/**
@@ -205,10 +213,11 @@ public ObjectId call() throws GitAPIException {
return null;
}
- ReflogWriter writer = new ReflogWriter(repo, true);
+ RefDirectory refdb = (RefDirectory) repo.getRefDatabase();
+ ReflogWriter writer = new ReflogWriter(refdb, true);
String stashLockRef = ReflogWriter.refLockFor(R_STASH);
- File stashLockFile = writer.logFor(stashLockRef);
- File stashFile = writer.logFor(R_STASH);
+ File stashLockFile = refdb.logFor(stashLockRef);
+ File stashFile = refdb.logFor(R_STASH);
if (stashLockFile.exists())
throw new JGitInternalException(JGitText.get().stashDropFailed,
new LockFailedException(stashFile));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
index f97dce9..b5c0b15 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
@@ -162,4 +162,4 @@ public Map<String, String> call() throws GitAPIException {
throw new JGitInternalException(e.getMessage(), e);
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
index 4d3dff0..4faaac2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
@@ -91,6 +91,10 @@ public class SubmoduleUpdateCommand extends
private CloneCommand.Callback callback;
+ private FetchCommand.Callback fetchCallback;
+
+ private boolean fetch = false;
+
/**
* @param repo
*/
@@ -114,6 +118,19 @@ public SubmoduleUpdateCommand setProgressMonitor(
}
/**
+ * Whether to fetch the submodules before we update them. By default, this
+ * is set to <code>false</code>
+ *
+ * @param fetch
+ * @return this command
+ * @since 4.9
+ */
+ public SubmoduleUpdateCommand setFetch(final boolean fetch) {
+ this.fetch = fetch;
+ return this;
+ }
+
+ /**
* Add repository-relative submodule path to initialize
*
* @param path
@@ -161,7 +178,7 @@ public Collection<String> call() throws InvalidConfigurationException,
continue;
Repository submoduleRepo = generator.getRepository();
- // Clone repository is not present
+ // Clone repository if not present
if (submoduleRepo == null) {
if (callback != null) {
callback.cloningSubmodule(generator.getPath());
@@ -175,6 +192,16 @@ public Collection<String> call() throws InvalidConfigurationException,
if (monitor != null)
clone.setProgressMonitor(monitor);
submoduleRepo = clone.call().getRepository();
+ } else if (this.fetch) {
+ if (fetchCallback != null) {
+ fetchCallback.fetchingSubmodule(generator.getPath());
+ }
+ FetchCommand fetchCommand = Git.wrap(submoduleRepo).fetch();
+ if (monitor != null) {
+ fetchCommand.setProgressMonitor(monitor);
+ }
+ configure(fetchCommand);
+ fetchCommand.call();
}
try (RevWalk walk = new RevWalk(submoduleRepo)) {
@@ -247,4 +274,18 @@ public SubmoduleUpdateCommand setCallback(CloneCommand.Callback callback) {
this.callback = callback;
return this;
}
+
+ /**
+ * Set status callback for submodule fetch operation.
+ *
+ * @param callback
+ * the callback
+ * @return {@code this}
+ * @since 4.9
+ */
+ public SubmoduleUpdateCommand setFetchCallback(
+ FetchCommand.Callback callback) {
+ this.fetchCallback = callback;
+ return this;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java
index 3d2e46b..1541df5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java
@@ -95,7 +95,7 @@ public C setCredentialsProvider(
/**
* @param timeout
- * the timeout used for the transport step
+ * the timeout (in seconds) used for the transport step
* @return {@code this}
*/
public C setTimeout(int timeout) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchApplyException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchApplyException.java
index 389c776..4329860 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchApplyException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchApplyException.java
@@ -44,9 +44,9 @@
/**
* Exception thrown when applying a patch fails
- *
+ *
* @since 2.0
- *
+ *
*/
public class PatchApplyException extends GitAPIException {
private static final long serialVersionUID = 1L;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchFormatException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchFormatException.java
index caff942..02ab423 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchFormatException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/PatchFormatException.java
@@ -50,9 +50,9 @@
/**
* Exception thrown when applying a patch fails due to an invalid format
- *
+ *
* @since 2.0
- *
+ *
*/
public class PatchFormatException extends GitAPIException {
private static final long serialVersionUID = 1L;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
index 905ad76..c256b73 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
@@ -193,4 +193,4 @@ public String toString() {
return key + "=" + value; //$NON-NLS-1$
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java
index 0810e31..d3826b3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java
@@ -1,5 +1,6 @@
/*
- * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
+ * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>,
+ * Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr)
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
@@ -48,6 +49,7 @@
import java.util.Map;
import org.eclipse.jgit.attributes.Attribute.State;
+import org.eclipse.jgit.lib.Constants;
/**
* Represents a set of attributes for a path
@@ -170,6 +172,26 @@ public String getValue(String key) {
return a != null ? a.getValue() : null;
}
+ /**
+ * Test if the given attributes implies to handle the related entry as a
+ * binary file (i.e. if the entry has an -merge or a merge=binary attribute)
+ * or if it can be content merged.
+ *
+ * @return <code>true</code> if the entry can be content merged,
+ * <code>false</code> otherwise
+ * @since 4.9
+ */
+ public boolean canBeContentMerged() {
+ if (isUnset(Constants.ATTR_MERGE)) {
+ return false;
+ } else if (isCustom(Constants.ATTR_MERGE)
+ && getValue(Constants.ATTR_MERGE)
+ .equals(Constants.ATTR_BUILTIN_BINARY_MERGER)) {
+ return false;
+ }
+ return true;
+ }
+
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java
index 3bf4179..8d928e3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java
@@ -144,7 +144,8 @@ public Attributes getAttributes() throws IOException {
mergeInfoAttributes(entryPath, isDirectory, attributes);
// Gets the attributes located on the current entry path
- mergePerDirectoryEntryAttributes(entryPath, isDirectory,
+ mergePerDirectoryEntryAttributes(entryPath, entryPath.lastIndexOf('/'),
+ isDirectory,
treeWalk.getTree(WorkingTreeIterator.class),
treeWalk.getTree(DirCacheIterator.class),
treeWalk.getTree(CanonicalTreeParser.class),
@@ -206,6 +207,8 @@ private void mergeInfoAttributes(String entryPath, boolean isDirectory,
* the path to test. The path must be relative to this attribute
* node's own repository path, and in repository path format
* (uses '/' and not '\').
+ * @param nameRoot
+ * index of the '/' preceeding the current level, or -1 if none
* @param isDirectory
* true if the target item is a directory.
* @param workingTreeIterator
@@ -217,7 +220,7 @@ private void mergeInfoAttributes(String entryPath, boolean isDirectory,
* @throws IOException
*/
private void mergePerDirectoryEntryAttributes(String entryPath,
- boolean isDirectory,
+ int nameRoot, boolean isDirectory,
@Nullable WorkingTreeIterator workingTreeIterator,
@Nullable DirCacheIterator dirCacheIterator,
@Nullable CanonicalTreeParser otherTree, Attributes result)
@@ -228,9 +231,12 @@ private void mergePerDirectoryEntryAttributes(String entryPath,
AttributesNode attributesNode = attributesNode(
treeWalk, workingTreeIterator, dirCacheIterator, otherTree);
if (attributesNode != null) {
- mergeAttributes(attributesNode, entryPath, isDirectory, result);
+ mergeAttributes(attributesNode,
+ entryPath.substring(nameRoot + 1), isDirectory,
+ result);
}
- mergePerDirectoryEntryAttributes(entryPath, isDirectory,
+ mergePerDirectoryEntryAttributes(entryPath,
+ entryPath.lastIndexOf('/', nameRoot - 1), isDirectory,
parentOf(workingTreeIterator), parentOf(dirCacheIterator),
parentOf(otherTree), result);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
index c9c69db..3cf5de8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010, Red Hat Inc.
+ * Copyright (C) 2010, 2017 Red Hat Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -210,7 +210,7 @@ public boolean isMatch(String relativeTarget, boolean isDirectory) {
return false;
if (relativeTarget.length() == 0)
return false;
- boolean match = matcher.matches(relativeTarget, isDirectory);
+ boolean match = matcher.matches(relativeTarget, isDirectory, true);
return match;
}
@@ -225,4 +225,4 @@ public String toString() {
return sb.toString();
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java
index 324b99e..ee70949 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java
@@ -54,12 +54,7 @@
/** Keeps track of diff related configuration options. */
public class DiffConfig {
/** Key for {@link Config#get(SectionParser)}. */
- public static final Config.SectionParser<DiffConfig> KEY = new SectionParser<DiffConfig>() {
- @Override
- public DiffConfig parse(final Config cfg) {
- return new DiffConfig(cfg);
- }
- };
+ public static final Config.SectionParser<DiffConfig> KEY = DiffConfig::new;
/** Permissible values for {@code diff.renames}. */
public static enum RenameDetectionType {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
index e1dfcff..5eb1942 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
@@ -525,4 +525,4 @@ public String toString() {
buf.append("]");
return buf.toString();
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java
index e1bda11..a3860de 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java
@@ -119,7 +119,7 @@ public class MyersDiff<S extends Sequence> {
public <S extends Sequence> void diffNonCommon(EditList edits,
HashedSequenceComparator<S> cmp, HashedSequence<S> a,
HashedSequence<S> b, Edit region) {
- new MyersDiff<S>(edits, cmp, a, b, region);
+ new MyersDiff<>(edits, cmp, a, b, region);
}
};
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
index aed76ac..a6ab9c8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -50,6 +50,7 @@
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -61,6 +62,7 @@
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.IndexWriteException;
import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
@@ -85,6 +87,7 @@
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FS.ExecutionResult;
import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.IntList;
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jgit.util.io.EolStreamTypeUtil;
@@ -151,6 +154,8 @@ public CheckoutMetadata(EolStreamType eolStreamType,
private boolean emptyDirCache;
+ private boolean performingCheckout;
+
/**
* @return a list of updated paths and smudgeFilterCommands
*/
@@ -432,10 +437,11 @@ void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
}
/**
- * Execute this checkout
+ * Execute this checkout. A {@link WorkingTreeModifiedEvent} is fired if the
+ * working tree was modified; even if the checkout fails.
*
* @return <code>false</code> if this method could not delete all the files
- * which should be deleted (e.g. because of of the files was
+ * which should be deleted (e.g. because one of the files was
* locked). In this case {@link #getToBeDeleted()} lists the files
* which should be tried to be deleted outside of this method.
* Although <code>false</code> is returned the checkout was
@@ -448,7 +454,17 @@ public boolean checkout() throws IOException {
try {
return doCheckout();
} finally {
- dc.unlock();
+ try {
+ dc.unlock();
+ } finally {
+ if (performingCheckout) {
+ WorkingTreeModifiedEvent event = new WorkingTreeModifiedEvent(
+ getUpdated().keySet(), getRemoved());
+ if (!event.isEmpty()) {
+ repo.fireEvent(event);
+ }
+ }
+ }
}
}
@@ -472,11 +488,13 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
// update our index
builder.finish();
+ performingCheckout = true;
File file = null;
String last = null;
// when deleting files process them in the opposite order as they have
// been reported. This ensures the files are deleted before we delete
// their parent folders
+ IntList nonDeleted = new IntList();
for (int i = removed.size() - 1; i >= 0; i--) {
String r = removed.get(i);
file = new File(repo.getWorkTree(), r);
@@ -486,25 +504,47 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
// a submodule, in which case we shall not attempt
// to delete it. A submodule is not empty, so it
// is safe to check this after a failed delete.
- if (!repo.getFS().isDirectory(file))
+ if (!repo.getFS().isDirectory(file)) {
+ nonDeleted.add(i);
toBeDeleted.add(r);
+ }
} else {
if (last != null && !isSamePrefix(r, last))
removeEmptyParents(new File(repo.getWorkTree(), last));
last = r;
}
}
- if (file != null)
+ if (file != null) {
removeEmptyParents(file);
-
- for (Map.Entry<String, CheckoutMetadata> e : updated.entrySet()) {
- String path = e.getKey();
- CheckoutMetadata meta = e.getValue();
- DirCacheEntry entry = dc.getEntry(path);
- if (!FileMode.GITLINK.equals(entry.getRawMode()))
- checkoutEntry(repo, entry, objectReader, false, meta);
}
-
+ removed = filterOut(removed, nonDeleted);
+ nonDeleted = null;
+ Iterator<Map.Entry<String, CheckoutMetadata>> toUpdate = updated
+ .entrySet().iterator();
+ Map.Entry<String, CheckoutMetadata> e = null;
+ try {
+ while (toUpdate.hasNext()) {
+ e = toUpdate.next();
+ String path = e.getKey();
+ CheckoutMetadata meta = e.getValue();
+ DirCacheEntry entry = dc.getEntry(path);
+ if (!FileMode.GITLINK.equals(entry.getRawMode())) {
+ checkoutEntry(repo, entry, objectReader, false, meta);
+ }
+ e = null;
+ }
+ } catch (Exception ex) {
+ // We didn't actually modify the current entry nor any that
+ // might follow.
+ if (e != null) {
+ toUpdate.remove();
+ }
+ while (toUpdate.hasNext()) {
+ e = toUpdate.next();
+ toUpdate.remove();
+ }
+ throw ex;
+ }
// commit the index builder - a new index is persisted
if (!builder.commit())
throw new IndexWriteException();
@@ -512,6 +552,36 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
return toBeDeleted.size() == 0;
}
+ private static ArrayList<String> filterOut(ArrayList<String> strings,
+ IntList indicesToRemove) {
+ int n = indicesToRemove.size();
+ if (n == strings.size()) {
+ return new ArrayList<>(0);
+ }
+ switch (n) {
+ case 0:
+ return strings;
+ case 1:
+ strings.remove(indicesToRemove.get(0));
+ return strings;
+ default:
+ int length = strings.size();
+ ArrayList<String> result = new ArrayList<>(length - n);
+ // Process indicesToRemove from the back; we know that it
+ // contains indices in descending order.
+ int j = n - 1;
+ int idx = indicesToRemove.get(j);
+ for (int i = 0; i < length; i++) {
+ if (i == idx) {
+ idx = (--j >= 0) ? indicesToRemove.get(j) : -1;
+ } else {
+ result.add(strings.get(i));
+ }
+ }
+ return result;
+ }
+ }
+
private static boolean isSamePrefix(String a, String b) {
int as = a.lastIndexOf('/');
int bs = b.lastIndexOf('/');
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptPackIndexException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptPackIndexException.java
new file mode 100644
index 0000000..65d83b3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptPackIndexException.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.errors;
+
+import org.eclipse.jgit.annotations.Nullable;
+
+/**
+ * Exception thrown when encounters a corrupt pack index file.
+ *
+ * @since 4.9
+ */
+public class CorruptPackIndexException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ /** The error type of a corrupt index file. */
+ public enum ErrorType {
+ /** Offset does not match index in pack file. */
+ MISMATCH_OFFSET,
+ /** CRC does not match CRC of the object data in pack file. */
+ MISMATCH_CRC,
+ /** CRC is not present in index file. */
+ MISSING_CRC,
+ /** Object in pack is not present in index file. */
+ MISSING_OBJ,
+ /** Object in index file is not present in pack file. */
+ UNKNOWN_OBJ,
+ }
+
+ private ErrorType errorType;
+
+ /**
+ * Report a specific error condition discovered in an index file.
+ *
+ * @param message
+ * the error message.
+ * @param errorType
+ * the error type of corruption.
+ */
+ public CorruptPackIndexException(String message, ErrorType errorType) {
+ super(message);
+ this.errorType = errorType;
+ }
+
+ /**
+ * Specific the reason of the corrupt index file.
+ *
+ * @return error condition or null.
+ */
+ @Nullable
+ public ErrorType getErrorType() {
+ return errorType;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/TooLargeObjectInPackException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/TooLargeObjectInPackException.java
index b5b1af5..ece76ed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/TooLargeObjectInPackException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/TooLargeObjectInPackException.java
@@ -92,4 +92,4 @@ public TooLargeObjectInPackException(long objectSize,
public TooLargeObjectInPackException(URIish uri, String s) {
super(uri.setPass(null) + ": " + s); //$NON-NLS-1$
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleLoadingException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleLoadingException.java
index 4f297b9..6cb332d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleLoadingException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleLoadingException.java
@@ -69,4 +69,4 @@ public TranslationBundleLoadingException(Class bundleClass, Locale locale, Excep
+ bundleClass.getName() + ", " + locale.toString() + "]", //$NON-NLS-1$ //$NON-NLS-2$
bundleClass, locale, cause);
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java
index 12ef533..cea03db 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java
@@ -53,6 +53,19 @@ public class ListenerList {
private final ConcurrentMap<Class<? extends RepositoryListener>, CopyOnWriteArrayList<ListenerHandle>> lists = new ConcurrentHashMap<>();
/**
+ * Register a {@link WorkingTreeModifiedListener}.
+ *
+ * @param listener
+ * the listener implementation.
+ * @return handle to later remove the listener.
+ * @since 4.9
+ */
+ public ListenerHandle addWorkingTreeModifiedListener(
+ WorkingTreeModifiedListener listener) {
+ return addListener(WorkingTreeModifiedListener.class, listener);
+ }
+
+ /**
* Register an IndexChangedListener.
*
* @param listener
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java
new file mode 100644
index 0000000..6517823
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.events;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.eclipse.jgit.annotations.NonNull;
+
+/**
+ * A {@link RepositoryEvent} describing changes to the working tree. It is fired
+ * whenever a {@link org.eclipse.jgit.dircache.DirCacheCheckout} modifies
+ * (adds/deletes/updates) files in the working tree.
+ *
+ * @since 4.9
+ */
+public class WorkingTreeModifiedEvent
+ extends RepositoryEvent<WorkingTreeModifiedListener> {
+
+ private Collection<String> modified;
+
+ private Collection<String> deleted;
+
+ /**
+ * Creates a new {@link WorkingTreeModifiedEvent} with the given
+ * collections.
+ *
+ * @param modified
+ * repository-relative paths that were added or updated
+ * @param deleted
+ * repository-relative paths that were deleted
+ */
+ public WorkingTreeModifiedEvent(Collection<String> modified,
+ Collection<String> deleted) {
+ this.modified = modified;
+ this.deleted = deleted;
+ }
+
+ /**
+ * Determines whether there are any changes recorded in this event.
+ *
+ * @return {@code true} if no files were modified or deleted, {@code false}
+ * otherwise
+ */
+ public boolean isEmpty() {
+ return (modified == null || modified.isEmpty())
+ && (deleted == null || deleted.isEmpty());
+ }
+
+ /**
+ * Retrieves the {@link Collection} of repository-relative paths of files
+ * that were modified (added or updated).
+ *
+ * @return the set
+ */
+ public @NonNull Collection<String> getModified() {
+ Collection<String> result = modified;
+ if (result == null) {
+ result = Collections.emptyList();
+ modified = result;
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the {@link Collection} of repository-relative paths of files
+ * that were deleted.
+ *
+ * @return the set
+ */
+ public @NonNull Collection<String> getDeleted() {
+ Collection<String> result = deleted;
+ if (result == null) {
+ result = Collections.emptyList();
+ deleted = result;
+ }
+ return result;
+ }
+
+ @Override
+ public Class<WorkingTreeModifiedListener> getListenerType() {
+ return WorkingTreeModifiedListener.class;
+ }
+
+ @Override
+ public void dispatch(WorkingTreeModifiedListener listener) {
+ listener.onWorkingTreeModified(this);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedListener.java
similarity index 78%
copy from org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedListener.java
index 98a2a94..402a900 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011, Google Inc.
+ * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -41,20 +41,21 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.eclipse.jgit.internal.storage.dfs;
+package org.eclipse.jgit.events;
-import java.util.concurrent.atomic.AtomicLong;
+/**
+ * Receives {@link WorkingTreeModifiedEvent}s, which are fired whenever a
+ * {@link org.eclipse.jgit.dircache.DirCacheCheckout} modifies
+ * (adds/deletes/updates) files in the working tree.
+ *
+ * @since 4.9
+ */
+public interface WorkingTreeModifiedListener extends RepositoryListener {
-final class DfsPackKey {
- final int hash;
-
- final AtomicLong cachedSize;
-
- DfsPackKey() {
- // Multiply by 31 here so we can more directly combine with another
- // value without doing the multiply there.
- //
- hash = System.identityHashCode(this) * 31;
- cachedSize = new AtomicLong();
- }
+ /**
+ * Respond to working tree modifications.
+ *
+ * @param event
+ */
+ void onWorkingTreeModified(WorkingTreeModifiedEvent event);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
index 1de8a0b..219babd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -49,7 +49,6 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.UnsupportedOperationException;
import java.net.URI;
import java.text.MessageFormat;
import java.util.ArrayList;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
index 62a6749..b684dd6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
@@ -167,4 +167,4 @@ protected void doRun() throws AbortedByHookException {
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
index ef67d49..7298a08 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
@@ -155,7 +155,7 @@ public boolean isMatch(String path, boolean directory) {
return false;
if (path.length() == 0)
return false;
- boolean match = matcher.matches(path, directory);
+ boolean match = matcher.matches(path, directory, false);
return match;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java
index 61f7b83..5b184cb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
+ * Copyright (C) 2014, 2017 Andrey Loskutov <loskutov@gmx.de>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -52,7 +52,8 @@ public interface IMatcher {
*/
public static final IMatcher NO_MATCH = new IMatcher() {
@Override
- public boolean matches(String path, boolean assumeDirectory) {
+ public boolean matches(String path, boolean assumeDirectory,
+ boolean pathMatch) {
return false;
}
@@ -71,9 +72,14 @@ public boolean matches(String segment, int startIncl, int endExcl,
* @param assumeDirectory
* true to assume this path as directory (even if it doesn't end
* with a slash)
+ * @param pathMatch
+ * {@code true} if the match is for the full path: prefix-only
+ * matches are not allowed, and {@link NameMatcher}s must match
+ * only the last component (if they can -- they may not, if they
+ * are anchored at the beginning)
* @return true if this matcher pattern matches given string
*/
- boolean matches(String path, boolean assumeDirectory);
+ boolean matches(String path, boolean assumeDirectory, boolean pathMatch);
/**
* Matches only part of given string
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java
index 0065123..9667837 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java
@@ -64,26 +64,59 @@ public class NameMatcher extends AbstractMatcher {
pattern = Strings.deleteBackslash(pattern);
}
beginning = pattern.length() == 0 ? false : pattern.charAt(0) == slash;
- if (!beginning)
+ if (!beginning) {
this.subPattern = pattern;
- else
+ } else {
this.subPattern = pattern.substring(1);
+ }
}
@Override
- public boolean matches(String path, boolean assumeDirectory) {
- int end = 0;
- int firstChar = 0;
- do {
- firstChar = getFirstNotSlash(path, end);
- end = getFirstSlash(path, firstChar);
- boolean match = matches(path, firstChar, end, assumeDirectory);
- if (match)
+ public boolean matches(String path, boolean assumeDirectory,
+ boolean pathMatch) {
+ // A NameMatcher's pattern does not contain a slash.
+ int start = 0;
+ int stop = path.length();
+ if (stop > 0 && path.charAt(0) == slash) {
+ start++;
+ }
+ if (pathMatch) {
+ // Can match only after the last slash
+ int lastSlash = path.lastIndexOf(slash, stop - 1);
+ if (lastSlash == stop - 1) {
+ // Skip trailing slash
+ lastSlash = path.lastIndexOf(slash, lastSlash - 1);
+ stop--;
+ }
+ boolean match;
+ if (lastSlash < start) {
+ match = matches(path, start, stop, assumeDirectory);
+ } else {
+ // Can't match if the path contains a slash if the pattern is
+ // anchored at the beginning
+ match = !beginning
+ && matches(path, lastSlash + 1, stop, assumeDirectory);
+ }
+ if (match && dirOnly) {
+ match = assumeDirectory;
+ }
+ return match;
+ }
+ while (start < stop) {
+ int end = path.indexOf(slash, start);
+ if (end < 0) {
+ end = stop;
+ }
+ if (end > start && matches(path, start, end, assumeDirectory)) {
// make sure the directory matches: either if we are done with
// segment and there is next one, or if the directory is assumed
- return !dirOnly ? true : (end > 0 && end != path.length())
- || assumeDirectory;
- } while (!beginning && end != path.length());
+ return !dirOnly || assumeDirectory || end < stop;
+ }
+ if (beginning) {
+ break;
+ }
+ start = end + 1;
+ }
return false;
}
@@ -92,25 +125,18 @@ public boolean matches(String segment, int startIncl, int endExcl,
boolean assumeDirectory) {
// faster local access, same as in string.indexOf()
String s = subPattern;
- if (s.length() != (endExcl - startIncl))
+ int length = s.length();
+ if (length != (endExcl - startIncl)) {
return false;
- for (int i = 0; i < s.length(); i++) {
+ }
+ for (int i = 0; i < length; i++) {
char c1 = s.charAt(i);
char c2 = segment.charAt(i + startIncl);
- if (c1 != c2)
+ if (c1 != c2) {
return false;
+ }
}
return true;
}
- private int getFirstNotSlash(String s, int start) {
- int slashIdx = s.indexOf(slash, start);
- return slashIdx == start ? start + 1 : start;
- }
-
- private int getFirstSlash(String s, int start) {
- int slashIdx = s.indexOf(slash, start);
- return slashIdx == -1 ? s.length() : slashIdx;
- }
-
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
index 65224ea..9b3a2aa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
@@ -52,7 +52,6 @@
import java.util.List;
import org.eclipse.jgit.errors.InvalidPatternException;
-import org.eclipse.jgit.ignore.FastIgnoreRule;
import org.eclipse.jgit.ignore.internal.Strings.PatternState;
/**
@@ -68,9 +67,10 @@ public class PathMatcher extends AbstractMatcher {
private final char slash;
- private boolean beginning;
+ private final boolean beginning;
- PathMatcher(String pattern, Character pathSeparator, boolean dirOnly)
+ private PathMatcher(String pattern, Character pathSeparator,
+ boolean dirOnly)
throws InvalidPatternException {
super(pattern, dirOnly);
slash = getPathSeparator(pathSeparator);
@@ -87,7 +87,7 @@ private boolean isSimplePathWithSegments(String path) {
&& count(path, slash, true) > 0;
}
- static private List<IMatcher> createMatchers(List<String> segments,
+ private static List<IMatcher> createMatchers(List<String> segments,
Character pathSeparator, boolean dirOnly)
throws InvalidPatternException {
List<IMatcher> matchers = new ArrayList<>(segments.size());
@@ -171,10 +171,12 @@ private static IMatcher createNameMatcher0(String segment,
}
@Override
- public boolean matches(String path, boolean assumeDirectory) {
- if (matchers == null)
- return simpleMatch(path, assumeDirectory);
- return iterate(path, 0, path.length(), assumeDirectory);
+ public boolean matches(String path, boolean assumeDirectory,
+ boolean pathMatch) {
+ if (matchers == null) {
+ return simpleMatch(path, assumeDirectory, pathMatch);
+ }
+ return iterate(path, 0, path.length(), assumeDirectory, pathMatch);
}
/*
@@ -182,31 +184,31 @@ public boolean matches(String path, boolean assumeDirectory) {
* wildcards or single segments (mean: this is multi-segment path which must
* be at the beginning of the another string)
*/
- private boolean simpleMatch(String path, boolean assumeDirectory) {
+ private boolean simpleMatch(String path, boolean assumeDirectory,
+ boolean pathMatch) {
boolean hasSlash = path.indexOf(slash) == 0;
- if (beginning && !hasSlash)
+ if (beginning && !hasSlash) {
path = slash + path;
-
- if (!beginning && hasSlash)
+ }
+ if (!beginning && hasSlash) {
path = path.substring(1);
-
- if (path.equals(pattern))
- // Exact match
- if (dirOnly && !assumeDirectory)
- // Directory expectations not met
- return false;
- else
- // Directory expectations met
- return true;
-
+ }
+ if (path.equals(pattern)) {
+ // Exact match: must meet directory expectations
+ return !dirOnly || assumeDirectory;
+ }
/*
* Add slashes for startsWith check. This avoids matching e.g.
* "/src/new" to /src/newfile" but allows "/src/new" to match
* "/src/new/newfile", as is the git standard
*/
- if (path.startsWith(pattern + FastIgnoreRule.PATH_SEPARATOR))
+ String prefix = pattern + slash;
+ if (pathMatch) {
+ return path.equals(prefix) && (!dirOnly || assumeDirectory);
+ }
+ if (path.startsWith(prefix)) {
return true;
-
+ }
return false;
}
@@ -217,61 +219,100 @@ public boolean matches(String segment, int startIncl, int endExcl,
"Path matcher works only on entire paths"); //$NON-NLS-1$
}
- boolean iterate(final String path, final int startIncl, final int endExcl,
- boolean assumeDirectory) {
+ private boolean iterate(final String path, final int startIncl,
+ final int endExcl, boolean assumeDirectory, boolean pathMatch) {
int matcher = 0;
int right = startIncl;
boolean match = false;
int lastWildmatch = -1;
+ // ** matches may get extended if a later match fails. When that
+ // happens, we must extend the ** by exactly one segment.
+ // wildmatchBacktrackPos records the end of the segment after a **
+ // match, so that we can reset correctly.
+ int wildmatchBacktrackPos = -1;
while (true) {
int left = right;
right = path.indexOf(slash, right);
if (right == -1) {
- if (left < endExcl)
+ if (left < endExcl) {
match = matches(matcher, path, left, endExcl,
assumeDirectory);
+ } else {
+ // a/** should not match a/ or a
+ match = match && matchers.get(matcher) != WILD;
+ }
if (match) {
- if (matcher == matchers.size() - 2
- && matchers.get(matcher + 1) == WILD)
- // ** can match *nothing*: a/b/** match also a/b
- return true;
if (matcher < matchers.size() - 1
&& matchers.get(matcher) == WILD) {
// ** can match *nothing*: a/**/b match also a/b
matcher++;
match = matches(matcher, path, left, endExcl,
assumeDirectory);
- } else if (dirOnly && !assumeDirectory)
+ } else if (dirOnly && !assumeDirectory) {
// Directory expectations not met
return false;
+ }
}
return match && matcher + 1 == matchers.size();
}
- if (right - left > 0)
+ if (wildmatchBacktrackPos < 0) {
+ wildmatchBacktrackPos = right;
+ }
+ if (right - left > 0) {
match = matches(matcher, path, left, right, assumeDirectory);
- else {
+ } else {
// path starts with slash???
right++;
continue;
}
if (match) {
- if (matchers.get(matcher) == WILD) {
+ boolean wasWild = matchers.get(matcher) == WILD;
+ if (wasWild) {
lastWildmatch = matcher;
+ wildmatchBacktrackPos = -1;
// ** can match *nothing*: a/**/b match also a/b
right = left - 1;
}
matcher++;
- if (matcher == matchers.size())
- return true;
- } else if (lastWildmatch != -1)
+ if (matcher == matchers.size()) {
+ // We had a prefix match here.
+ if (!pathMatch) {
+ return true;
+ } else {
+ if (right == endExcl - 1) {
+ // Extra slash at the end: actually a full match.
+ // Must meet directory expectations
+ return !dirOnly || assumeDirectory;
+ }
+ // Prefix matches only if pattern ended with /**
+ if (wasWild) {
+ return true;
+ }
+ if (lastWildmatch >= 0) {
+ // Consider pattern **/x and input x/x.
+ // We've matched the prefix x/ so far: we
+ // must try to extend the **!
+ matcher = lastWildmatch + 1;
+ right = wildmatchBacktrackPos;
+ wildmatchBacktrackPos = -1;
+ } else {
+ return false;
+ }
+ }
+ }
+ } else if (lastWildmatch != -1) {
matcher = lastWildmatch + 1;
- else
+ right = wildmatchBacktrackPos;
+ wildmatchBacktrackPos = -1;
+ } else {
return false;
+ }
right++;
}
}
- boolean matches(int matcherIdx, String path, int startIncl, int endExcl,
+ private boolean matches(int matcherIdx, String path, int startIncl,
+ int endExcl,
boolean assumeDirectory) {
IMatcher matcher = matchers.get(matcherIdx);
return matcher.matches(path, startIncl, endExcl, assumeDirectory);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
index da482fa..800cdb9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
+ * Copyright (C) 2014, 2017 Andrey Loskutov <loskutov@gmx.de>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -123,12 +123,15 @@ public static boolean isDirectoryPattern(String pattern) {
static int count(String s, char c, boolean ignoreFirstLast) {
int start = 0;
int count = 0;
- while (true) {
+ int length = s.length();
+ while (start < length) {
start = s.indexOf(c, start);
- if (start == -1)
+ if (start == -1) {
break;
- if (!ignoreFirstLast || (start != 0 && start != s.length()))
+ }
+ if (!ignoreFirstLast || (start != 0 && start != length - 1)) {
count++;
+ }
start++;
}
return count;
@@ -360,7 +363,10 @@ && isLetter(lookAhead(pattern, i)))
case '[':
if (in_brackets > 0) {
- sb.append('\\').append('[');
+ if (!seenEscape) {
+ sb.append('\\');
+ }
+ sb.append('[');
ignoreLastBracket = true;
} else {
if (!seenEscape) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java
index 93ea13c..363b3ce 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java
@@ -62,7 +62,8 @@ private WildMatcher() {
}
@Override
- public final boolean matches(String path, boolean assumeDirectory) {
+ public final boolean matches(String path, boolean assumeDirectory,
+ boolean pathMatch) {
return true;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index aeda4a0..2fc5501 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -148,6 +148,7 @@ public static JGitText get() {
/***/ public String cannotParseGitURIish;
/***/ public String cannotPullOnARepoWithState;
/***/ public String cannotRead;
+ /***/ public String cannotReadBackDelta;
/***/ public String cannotReadBlob;
/***/ public String cannotReadCommit;
/***/ public String cannotReadFile;
@@ -180,6 +181,7 @@ public static JGitText get() {
/***/ public String closeLockTokenFailed;
/***/ public String closed;
/***/ public String collisionOn;
+ /***/ public String commandClosedStderrButDidntExit;
/***/ public String commandRejectedByHook;
/***/ public String commandWasCalledInTheWrongState;
/***/ public String commitAlreadyExists;
@@ -270,12 +272,14 @@ public static JGitText get() {
/***/ public String createBranchFailedUnknownReason;
/***/ public String createBranchUnexpectedResult;
/***/ public String createNewFileFailed;
+ /***/ public String createRequiresZeroOldId;
/***/ public String credentialPassword;
/***/ public String credentialUsername;
/***/ public String daemonAlreadyRunning;
/***/ public String daysAgo;
/***/ public String deleteBranchUnexpectedResult;
/***/ public String deleteFileFailed;
+ /***/ public String deleteRequiresZeroNewId;
/***/ public String deleteTagUnexpectedResult;
/***/ public String deletingNotSupported;
/***/ public String destinationIsNotAWildcard;
@@ -304,6 +308,7 @@ public static JGitText get() {
/***/ public String encryptionOnlyPBE;
/***/ public String endOfFileInEscape;
/***/ public String entryNotFoundByPath;
+ /***/ public String enumValueNotSupported0;
/***/ public String enumValueNotSupported2;
/***/ public String enumValueNotSupported3;
/***/ public String enumValuesNotAvailable;
@@ -319,6 +324,7 @@ public static JGitText get() {
/***/ public String exceptionCaughtDuringExecutionOfAddCommand;
/***/ public String exceptionCaughtDuringExecutionOfArchiveCommand;
/***/ public String exceptionCaughtDuringExecutionOfCherryPickCommand;
+ /***/ public String exceptionCaughtDuringExecutionOfCommand;
/***/ public String exceptionCaughtDuringExecutionOfCommitCommand;
/***/ public String exceptionCaughtDuringExecutionOfFetchCommand;
/***/ public String exceptionCaughtDuringExecutionOfLsRemoteCommand;
@@ -329,7 +335,6 @@ public static JGitText get() {
/***/ public String exceptionCaughtDuringExecutionOfRevertCommand;
/***/ public String exceptionCaughtDuringExecutionOfRmCommand;
/***/ public String exceptionCaughtDuringExecutionOfTagCommand;
- /***/ public String exceptionCaughtDuringExcecutionOfCommand;
/***/ public String exceptionHookExecutionInterrupted;
/***/ public String exceptionOccurredDuringAddingOfOptionToALogCommand;
/***/ public String exceptionOccurredDuringReadingOfGIT_DIR;
@@ -367,6 +372,8 @@ public static JGitText get() {
/***/ public String gitmodulesNotFound;
/***/ public String headRequiredToStash;
/***/ public String hoursAgo;
+ /***/ public String httpConfigCannotNormalizeURL;
+ /***/ public String httpConfigInvalidURL;
/***/ public String hugeIndexesAreNotSupportedByJgitYet;
/***/ public String hunkBelongsToAnotherFile;
/***/ public String hunkDisconnectedFromFile;
@@ -427,11 +434,16 @@ public static JGitText get() {
/***/ public String invalidPathPeriodAtEndWindows;
/***/ public String invalidPathSpaceAtEndWindows;
/***/ public String invalidPathReservedOnWindows;
+ /***/ public String invalidRedirectLocation;
/***/ public String invalidReflogRevision;
/***/ public String invalidRefName;
+ /***/ public String invalidReftableBlock;
+ /***/ public String invalidReftableCRC;
+ /***/ public String invalidReftableFile;
/***/ public String invalidRemote;
/***/ public String invalidShallowObject;
/***/ public String invalidStageForPath;
+ /***/ public String invalidSystemProperty;
/***/ public String invalidTagOption;
/***/ public String invalidTimeout;
/***/ public String invalidTimeUnitValue2;
@@ -471,8 +483,11 @@ public static JGitText get() {
/***/ public String mergeRecursiveTooManyMergeBasesFor;
/***/ public String messageAndTaggerNotAllowedInUnannotatedTags;
/***/ public String minutesAgo;
+ /***/ public String mismatchOffset;
+ /***/ public String mismatchCRC;
/***/ public String missingAccesskey;
/***/ public String missingConfigurationForKey;
+ /***/ public String missingCRC;
/***/ public String missingDeltaBase;
/***/ public String missingForwardImageInGITBinaryPatch;
/***/ public String missingObject;
@@ -490,6 +505,7 @@ public static JGitText get() {
/***/ public String needPackOut;
/***/ public String needsAtLeastOneEntry;
/***/ public String needsWorkdir;
+ /***/ public String newIdMustNotBeNull;
/***/ public String newlineInQuotesNotAllowed;
/***/ public String noApplyInDelete;
/***/ public String noClosingBracket;
@@ -523,6 +539,7 @@ public static JGitText get() {
/***/ public String objectNotFoundIn;
/***/ public String obtainingCommitsForCherryPick;
/***/ public String offsetWrittenDeltaBaseForObjectNotFoundInAPack;
+ /***/ public String oldIdMustNotBeNull;
/***/ public String onlyAlreadyUpToDateAndFastForwardMergesAreAvailable;
/***/ public String onlyOneFetchSupported;
/***/ public String onlyOneOperationCallPerConnectionIsSupported;
@@ -530,6 +547,7 @@ public static JGitText get() {
/***/ public String openingConnection;
/***/ public String operationCanceled;
/***/ public String outputHasAlreadyBeenStarted;
+ /***/ public String overflowedReftableBlock;
/***/ public String packChecksumMismatch;
/***/ public String packCorruptedWhileWritingToFilesystem;
/***/ public String packDoesNotMatchIndex;
@@ -557,6 +575,7 @@ public static JGitText get() {
/***/ public String pathIsNotInWorkingDir;
/***/ public String pathNotConfigured;
/***/ public String peeledLineBeforeRef;
+ /***/ public String peeledRefIsRequired;
/***/ public String peerDidNotSupplyACompleteObjectGraph;
/***/ public String personIdentEmailNonNull;
/***/ public String personIdentNameNonNull;
@@ -585,6 +604,11 @@ public static JGitText get() {
/***/ public String receivePackInvalidLimit;
/***/ public String receivePackTooLarge;
/***/ public String receivingObjects;
+ /***/ public String redirectBlocked;
+ /***/ public String redirectHttp;
+ /***/ public String redirectLimitExceeded;
+ /***/ public String redirectLocationMissing;
+ /***/ public String redirectsOff;
/***/ public String refAlreadyExists;
/***/ public String refAlreadyExists1;
/***/ public String reflogEntryNotFound;
@@ -650,6 +674,15 @@ public static JGitText get() {
/***/ public String sourceRefDoesntResolveToAnyObject;
/***/ public String sourceRefNotSpecifiedForRefspec;
/***/ public String squashCommitNotUpdatingHEAD;
+ /***/ public String sshUserNameError;
+ /***/ public String sslFailureExceptionMessage;
+ /***/ public String sslFailureInfo;
+ /***/ public String sslFailureCause;
+ /***/ public String sslFailureTrustExplanation;
+ /***/ public String sslTrustAlways;
+ /***/ public String sslTrustForRepo;
+ /***/ public String sslTrustNow;
+ /***/ public String sslVerifyCannotSave;
/***/ public String staleRevFlagsOn;
/***/ public String startingReadStageWithoutWrittenRequestDataPendingIsNotSupported;
/***/ public String stashApplyConflict;
@@ -661,6 +694,7 @@ public static JGitText get() {
/***/ public String stashDropDeleteRefFailed;
/***/ public String stashDropFailed;
/***/ public String stashDropMissingReflog;
+ /***/ public String stashDropNotSupported;
/***/ public String stashFailed;
/***/ public String stashResolveFailed;
/***/ public String statelessRPCRequiresOptionToBeEnabled;
@@ -681,6 +715,7 @@ public static JGitText get() {
/***/ public String tagOnRepoWithoutHEADCurrentlyNotSupported;
/***/ public String transactionAborted;
/***/ public String theFactoryMustNotBeNull;
+ /***/ public String threadInterruptedWhileRunning;
/***/ public String timeIsUncertain;
/***/ public String timerAlreadyTerminated;
/***/ public String tooManyCommands;
@@ -713,14 +748,17 @@ public static JGitText get() {
/***/ public String tSizeMustBeGreaterOrEqual1;
/***/ public String unableToCheckConnectivity;
/***/ public String unableToCreateNewObject;
+ /***/ public String unableToReadPackfile;
/***/ public String unableToRemovePath;
/***/ public String unableToStore;
/***/ public String unableToWrite;
/***/ public String unauthorized;
+ /***/ public String underflowedReftableBlock;
/***/ public String unencodeableFile;
/***/ public String unexpectedCompareResult;
/***/ public String unexpectedEndOfConfigFile;
/***/ public String unexpectedEndOfInput;
+ /***/ public String unexpectedEofInPack;
/***/ public String unexpectedHunkTrailer;
/***/ public String unexpectedOddResult;
/***/ public String unexpectedRefReport;
@@ -731,6 +769,7 @@ public static JGitText get() {
/***/ public String unknownHost;
/***/ public String unknownIndexVersionOrCorruptIndex;
/***/ public String unknownObject;
+ /***/ public String unknownObjectInIndex;
/***/ public String unknownObjectType;
/***/ public String unknownObjectType2;
/***/ public String unknownRepositoryFormat;
@@ -753,7 +792,9 @@ public static JGitText get() {
/***/ public String unsupportedOperationNotAddAtEnd;
/***/ public String unsupportedPackIndexVersion;
/***/ public String unsupportedPackVersion;
+ /***/ public String unsupportedReftableVersion;
/***/ public String unsupportedRepositoryDescription;
+ /***/ public String updateRequiresOldIdAndNewId;
/***/ public String updatingHeadFailed;
/***/ public String updatingReferences;
/***/ public String updatingRefFailed;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java
new file mode 100644
index 0000000..588ed9b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.internal.fsck;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.CorruptPackIndexException;
+import org.eclipse.jgit.errors.CorruptPackIndexException.ErrorType;
+import org.eclipse.jgit.lib.ObjectChecker;
+import org.eclipse.jgit.lib.ObjectId;
+
+/** Holds all fsck errors of a git repository. */
+public class FsckError {
+ /** Represents a corrupt object. */
+ public static class CorruptObject {
+ final ObjectId id;
+
+ final int type;
+
+ ObjectChecker.ErrorType errorType;
+
+ /**
+ * @param id
+ * the object identifier.
+ * @param type
+ * type of the object.
+ */
+ public CorruptObject(ObjectId id, int type) {
+ this.id = id;
+ this.type = type;
+ }
+
+ void setErrorType(ObjectChecker.ErrorType errorType) {
+ this.errorType = errorType;
+ }
+
+ /** @return identifier of the object. */
+ public ObjectId getId() {
+ return id;
+ }
+
+ /** @return type of the object. */
+ public int getType() {
+ return type;
+ }
+
+ /** @return error type of the corruption. */
+ @Nullable
+ public ObjectChecker.ErrorType getErrorType() {
+ return errorType;
+ }
+ }
+
+ /** Represents a corrupt pack index file. */
+ public static class CorruptIndex {
+ String fileName;
+
+ CorruptPackIndexException.ErrorType errorType;
+
+ /**
+ * @param fileName
+ * the file name of the pack index.
+ * @param errorType
+ * the type of error as reported in
+ * {@link CorruptPackIndexException}.
+ */
+ public CorruptIndex(String fileName, ErrorType errorType) {
+ this.fileName = fileName;
+ this.errorType = errorType;
+ }
+
+ /** @return the file name of the index file. */
+ public String getFileName() {
+ return fileName;
+ }
+
+ /** @return the error type of the corruption. */
+ public ErrorType getErrorType() {
+ return errorType;
+ }
+ }
+
+ private final Set<CorruptObject> corruptObjects = new HashSet<>();
+
+ private final Set<ObjectId> missingObjects = new HashSet<>();
+
+ private final Set<CorruptIndex> corruptIndices = new HashSet<>();
+
+ private final Set<String> nonCommitHeads = new HashSet<>();
+
+ /** @return corrupt objects from all pack files. */
+ public Set<CorruptObject> getCorruptObjects() {
+ return corruptObjects;
+ }
+
+ /** @return missing objects that should present in pack files. */
+ public Set<ObjectId> getMissingObjects() {
+ return missingObjects;
+ }
+
+ /** @return corrupt index files associated with the packs. */
+ public Set<CorruptIndex> getCorruptIndices() {
+ return corruptIndices;
+ }
+
+ /** @return refs/heads/* point to non-commit object. */
+ public Set<String> getNonCommitHeads() {
+ return nonCommitHeads;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java
new file mode 100644
index 0000000..3a678a7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.fsck;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.zip.CRC32;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.CorruptPackIndexException;
+import org.eclipse.jgit.errors.CorruptPackIndexException.ErrorType;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.fsck.FsckError.CorruptObject;
+import org.eclipse.jgit.internal.storage.dfs.ReadableChannel;
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectChecker;
+import org.eclipse.jgit.lib.ObjectDatabase;
+import org.eclipse.jgit.lib.ObjectIdOwnerMap;
+import org.eclipse.jgit.transport.PackParser;
+import org.eclipse.jgit.transport.PackedObjectInfo;
+
+/** A read-only pack parser for object validity checking. */
+public class FsckPackParser extends PackParser {
+ private final CRC32 crc;
+
+ private final ReadableChannel channel;
+
+ private final Set<CorruptObject> corruptObjects = new HashSet<>();
+
+ private long expectedObjectCount = -1L;
+
+ private long offset;
+
+ private int blockSize;
+
+ /**
+ * @param db
+ * the object database which stores repository's data.
+ * @param channel
+ * readable channel of the pack file.
+ */
+ public FsckPackParser(ObjectDatabase db, ReadableChannel channel) {
+ super(db, Channels.newInputStream(channel));
+ this.channel = channel;
+ setCheckObjectCollisions(false);
+ this.crc = new CRC32();
+ this.blockSize = channel.blockSize() > 0 ? channel.blockSize() : 65536;
+ }
+
+ @Override
+ protected void onPackHeader(long objCnt) throws IOException {
+ if (expectedObjectCount >= 0) {
+ // Some DFS pack files don't contain the correct object count, e.g.
+ // INSERT/RECEIVE packs don't always contain the correct object
+ // count in their headers. Overwrite the expected object count
+ // after parsing the pack header.
+ setExpectedObjectCount(expectedObjectCount);
+ }
+ }
+
+ @Override
+ protected void onBeginWholeObject(long streamPosition, int type,
+ long inflatedSize) throws IOException {
+ crc.reset();
+ }
+
+ @Override
+ protected void onObjectHeader(Source src, byte[] raw, int pos, int len)
+ throws IOException {
+ crc.update(raw, pos, len);
+ }
+
+ @Override
+ protected void onObjectData(Source src, byte[] raw, int pos, int len)
+ throws IOException {
+ crc.update(raw, pos, len);
+ }
+
+ @Override
+ protected void onEndWholeObject(PackedObjectInfo info) throws IOException {
+ info.setCRC((int) crc.getValue());
+ }
+
+ @Override
+ protected void onBeginOfsDelta(long deltaStreamPosition,
+ long baseStreamPosition, long inflatedSize) throws IOException {
+ crc.reset();
+ }
+
+ @Override
+ protected void onBeginRefDelta(long deltaStreamPosition, AnyObjectId baseId,
+ long inflatedSize) throws IOException {
+ crc.reset();
+ }
+
+ @Override
+ protected UnresolvedDelta onEndDelta() throws IOException {
+ UnresolvedDelta delta = new UnresolvedDelta();
+ delta.setCRC((int) crc.getValue());
+ return delta;
+ }
+
+ @Override
+ protected void onInflatedObjectData(PackedObjectInfo obj, int typeCode,
+ byte[] data) throws IOException {
+ // FsckPackParser ignores this event.
+ }
+
+ @Override
+ protected void verifySafeObject(final AnyObjectId id, final int type,
+ final byte[] data) {
+ try {
+ super.verifySafeObject(id, type, data);
+ } catch (CorruptObjectException e) {
+ // catch the exception and continue parse the pack file
+ CorruptObject o = new CorruptObject(id.toObjectId(), type);
+ if (e.getErrorType() != null) {
+ o.setErrorType(e.getErrorType());
+ }
+ corruptObjects.add(o);
+ }
+ }
+
+ @Override
+ protected void onPackFooter(byte[] hash) throws IOException {
+ // Do nothing.
+ }
+
+ @Override
+ protected boolean onAppendBase(int typeCode, byte[] data,
+ PackedObjectInfo info) throws IOException {
+ // Do nothing.
+ return false;
+ }
+
+ @Override
+ protected void onEndThinPack() throws IOException {
+ // Do nothing.
+ }
+
+ @Override
+ protected ObjectTypeAndSize seekDatabase(PackedObjectInfo obj,
+ ObjectTypeAndSize info) throws IOException {
+ crc.reset();
+ offset = obj.getOffset();
+ return readObjectHeader(info);
+ }
+
+ @Override
+ protected ObjectTypeAndSize seekDatabase(UnresolvedDelta delta,
+ ObjectTypeAndSize info) throws IOException {
+ crc.reset();
+ offset = delta.getOffset();
+ return readObjectHeader(info);
+ }
+
+ @Override
+ protected int readDatabase(byte[] dst, int pos, int cnt)
+ throws IOException {
+ // read from input instead of database.
+ int n = read(offset, dst, pos, cnt);
+ if (n > 0) {
+ offset += n;
+ }
+ return n;
+ }
+
+ int read(long channelPosition, byte[] dst, int pos, int cnt)
+ throws IOException {
+ long block = channelPosition / blockSize;
+ byte[] bytes = readFromChannel(block);
+ if (bytes == null) {
+ return -1;
+ }
+ int offs = (int) (channelPosition - block * blockSize);
+ int bytesToCopy = Math.min(cnt, bytes.length - offs);
+ if (bytesToCopy < 1) {
+ return -1;
+ }
+ System.arraycopy(bytes, offs, dst, pos, bytesToCopy);
+ return bytesToCopy;
+ }
+
+ private byte[] readFromChannel(long block) throws IOException {
+ channel.position(block * blockSize);
+ ByteBuffer buf = ByteBuffer.allocate(blockSize);
+ int totalBytesRead = 0;
+ while (totalBytesRead < blockSize) {
+ int bytesRead = channel.read(buf);
+ if (bytesRead == -1) {
+ if (totalBytesRead == 0) {
+ return null;
+ }
+ return Arrays.copyOf(buf.array(), totalBytesRead);
+ }
+ totalBytesRead += bytesRead;
+ }
+ return buf.array();
+ }
+
+ @Override
+ protected boolean checkCRC(int oldCRC) {
+ return oldCRC == (int) crc.getValue();
+ }
+
+ @Override
+ protected void onStoreStream(byte[] raw, int pos, int len)
+ throws IOException {
+ // Do nothing.
+ }
+
+ /**
+ * @return corrupt objects that reported by {@link ObjectChecker}.
+ */
+ public Set<CorruptObject> getCorruptObjects() {
+ return corruptObjects;
+ }
+
+ /**
+ * Verify the existing index file with all objects from the pack.
+ *
+ * @param idx
+ * index file associate with the pack
+ * @throws CorruptPackIndexException
+ * when the index file is corrupt.
+ */
+ public void verifyIndex(PackIndex idx)
+ throws CorruptPackIndexException {
+ ObjectIdOwnerMap<ObjFromPack> inPack = new ObjectIdOwnerMap<>();
+ for (int i = 0; i < getObjectCount(); i++) {
+ PackedObjectInfo entry = getObject(i);
+ inPack.add(new ObjFromPack(entry));
+
+ long offs = idx.findOffset(entry);
+ if (offs == -1) {
+ throw new CorruptPackIndexException(
+ MessageFormat.format(JGitText.get().missingObject,
+ Integer.valueOf(entry.getType()),
+ entry.getName()),
+ ErrorType.MISSING_OBJ);
+ } else if (offs != entry.getOffset()) {
+ throw new CorruptPackIndexException(MessageFormat
+ .format(JGitText.get().mismatchOffset, entry.getName()),
+ ErrorType.MISMATCH_OFFSET);
+ }
+
+ try {
+ if (idx.hasCRC32Support()
+ && (int) idx.findCRC32(entry) != entry.getCRC()) {
+ throw new CorruptPackIndexException(
+ MessageFormat.format(JGitText.get().mismatchCRC,
+ entry.getName()),
+ ErrorType.MISMATCH_CRC);
+ }
+ } catch (MissingObjectException e) {
+ throw new CorruptPackIndexException(MessageFormat
+ .format(JGitText.get().missingCRC, entry.getName()),
+ ErrorType.MISSING_CRC);
+ }
+ }
+
+ for (MutableEntry entry : idx) {
+ if (!inPack.contains(entry.toObjectId())) {
+ throw new CorruptPackIndexException(MessageFormat.format(
+ JGitText.get().unknownObjectInIndex, entry.name()),
+ ErrorType.UNKNOWN_OBJ);
+ }
+ }
+ }
+
+ /**
+ * Set the object count for overwriting the expected object count from pack
+ * header.
+ *
+ * @param objectCount
+ * the actual expected object count.
+ */
+ public void overwriteObjectCount(long objectCount) {
+ this.expectedObjectCount = objectCount;
+ }
+
+ static class ObjFromPack extends ObjectIdOwnerMap.Entry {
+ ObjFromPack(AnyObjectId id) {
+ super(id);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/package-info.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/package-info.java
new file mode 100644
index 0000000..361b61f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Git fsck support.
+ */
+package org.eclipse.jgit.internal.fsck;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java
new file mode 100644
index 0000000..b9758bd
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.PackInvalidException;
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+
+/** Block based file stored in {@link DfsBlockCache}. */
+abstract class BlockBasedFile {
+ /** Cache that owns this file and its data. */
+ final DfsBlockCache cache;
+
+ /** Unique identity of this file while in-memory. */
+ final DfsStreamKey key;
+
+ /** Description of the associated pack file's storage. */
+ final DfsPackDescription desc;
+ final PackExt ext;
+
+ /**
+ * Preferred alignment for loading blocks from the backing file.
+ * <p>
+ * It is initialized to 0 and filled in on the first read made from the
+ * file. Block sizes may be odd, e.g. 4091, caused by the underling DFS
+ * storing 4091 user bytes and 5 bytes block metadata into a lower level
+ * 4096 byte block on disk.
+ */
+ volatile int blockSize;
+
+ /**
+ * Total number of bytes in this pack file.
+ * <p>
+ * This field initializes to -1 and gets populated when a block is loaded.
+ */
+ volatile long length;
+
+ /** True once corruption has been detected that cannot be worked around. */
+ volatile boolean invalid;
+
+ BlockBasedFile(DfsBlockCache cache, DfsPackDescription desc, PackExt ext) {
+ this.cache = cache;
+ this.key = desc.getStreamKey(ext);
+ this.desc = desc;
+ this.ext = ext;
+ }
+
+ String getFileName() {
+ return desc.getFileName(ext);
+ }
+
+ boolean invalid() {
+ return invalid;
+ }
+
+ void setInvalid() {
+ invalid = true;
+ }
+
+ void setBlockSize(int newSize) {
+ blockSize = newSize;
+ }
+
+ long alignToBlock(long pos) {
+ int size = blockSize;
+ if (size == 0)
+ size = cache.getBlockSize();
+ return (pos / size) * size;
+ }
+
+ int blockSize(ReadableChannel rc) {
+ // If the block alignment is not yet known, discover it. Prefer the
+ // larger size from either the cache or the file itself.
+ int size = blockSize;
+ if (size == 0) {
+ size = rc.blockSize();
+ if (size <= 0)
+ size = cache.getBlockSize();
+ else if (size < cache.getBlockSize())
+ size = (cache.getBlockSize() / size) * size;
+ blockSize = size;
+ }
+ return size;
+ }
+
+ DfsBlock getOrLoadBlock(long pos, DfsReader ctx) throws IOException {
+ return cache.getOrLoad(this, pos, ctx, null);
+ }
+
+ DfsBlock readOneBlock(long pos, DfsReader ctx,
+ @Nullable ReadableChannel fileChannel) throws IOException {
+ if (invalid)
+ throw new PackInvalidException(getFileName());
+
+ ctx.stats.readBlock++;
+ long start = System.nanoTime();
+ ReadableChannel rc = fileChannel != null ? fileChannel
+ : ctx.db.openFile(desc, ext);
+ try {
+ int size = blockSize(rc);
+ pos = (pos / size) * size;
+
+ // If the size of the file is not yet known, try to discover it.
+ // Channels may choose to return -1 to indicate they don't
+ // know the length yet, in this case read up to the size unit
+ // given by the caller, then recheck the length.
+ long len = length;
+ if (len < 0) {
+ len = rc.size();
+ if (0 <= len)
+ length = len;
+ }
+
+ if (0 <= len && len < pos + size)
+ size = (int) (len - pos);
+ if (size <= 0)
+ throw new EOFException(MessageFormat.format(
+ DfsText.get().shortReadOfBlock, Long.valueOf(pos),
+ getFileName(), Long.valueOf(0), Long.valueOf(0)));
+
+ byte[] buf = new byte[size];
+ rc.position(pos);
+ int cnt = read(rc, ByteBuffer.wrap(buf, 0, size));
+ ctx.stats.readBlockBytes += cnt;
+ if (cnt != size) {
+ if (0 <= len) {
+ throw new EOFException(MessageFormat.format(
+ DfsText.get().shortReadOfBlock, Long.valueOf(pos),
+ getFileName(), Integer.valueOf(size),
+ Integer.valueOf(cnt)));
+ }
+
+ // Assume the entire thing was read in a single shot, compact
+ // the buffer to only the space required.
+ byte[] n = new byte[cnt];
+ System.arraycopy(buf, 0, n, 0, n.length);
+ buf = n;
+ } else if (len < 0) {
+ // With no length at the start of the read, the channel should
+ // have the length available at the end.
+ length = len = rc.size();
+ }
+
+ return new DfsBlock(key, pos, buf);
+ } finally {
+ if (rc != fileChannel) {
+ rc.close();
+ }
+ ctx.stats.readBlockMicros += elapsedMicros(start);
+ }
+ }
+
+ static int read(ReadableChannel rc, ByteBuffer buf) throws IOException {
+ int n;
+ do {
+ n = rc.read(buf);
+ } while (0 < n && buf.hasRemaining());
+ return buf.position();
+ }
+
+ static long elapsedMicros(long start) {
+ return (System.nanoTime() - start) / 1000L;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCache.java
index 64a63d7..bd4b4d2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DeltaBaseCache.java
@@ -75,7 +75,7 @@ private static int hash(long position) {
table = new Entry[1 << TABLE_BITS];
}
- Entry get(DfsPackKey key, long position) {
+ Entry get(DfsStreamKey key, long position) {
Entry e = table[hash(position)];
for (; e != null; e = e.tableNext) {
if (e.offset == position && key.equals(e.pack)) {
@@ -86,7 +86,7 @@ Entry get(DfsPackKey key, long position) {
return null;
}
- void put(DfsPackKey key, long offset, int objectType, byte[] data) {
+ void put(DfsStreamKey key, long offset, int objectType, byte[] data) {
if (data.length > maxByteCount)
return; // Too large to cache.
@@ -189,7 +189,7 @@ int getMemoryUsedByTableForTest() {
}
static class Entry {
- final DfsPackKey pack;
+ final DfsStreamKey pack;
final long offset;
final int type;
final byte[] data;
@@ -198,7 +198,7 @@ static class Entry {
Entry lruPrev;
Entry lruNext;
- Entry(DfsPackKey key, long offset, int type, byte[] data) {
+ Entry(DfsStreamKey key, long offset, int type, byte[] data) {
this.pack = key;
this.offset = offset;
this.type = type;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java
index 4a33fb8..62a9be3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java
@@ -46,24 +46,22 @@
package org.eclipse.jgit.internal.storage.dfs;
import java.io.IOException;
+import java.nio.ByteBuffer;
import java.util.zip.CRC32;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
-/** A cached slice of a {@link DfsPackFile}. */
+/** A cached slice of a {@link BlockBasedFile}. */
final class DfsBlock {
- final DfsPackKey pack;
-
+ final DfsStreamKey stream;
final long start;
-
final long end;
-
private final byte[] block;
- DfsBlock(DfsPackKey p, long pos, byte[] buf) {
- pack = p;
+ DfsBlock(DfsStreamKey p, long pos, byte[] buf) {
+ stream = p;
start = pos;
end = pos + buf.length;
block = buf;
@@ -73,8 +71,14 @@ int size() {
return block.length;
}
- boolean contains(DfsPackKey want, long pos) {
- return pack == want && start <= pos && pos < end;
+ ByteBuffer zeroCopyByteBuffer(int n) {
+ ByteBuffer b = ByteBuffer.wrap(block);
+ b.position(n);
+ return b;
+ }
+
+ boolean contains(DfsStreamKey want, long pos) {
+ return stream.equals(want) && start <= pos && pos < end;
}
int copy(long pos, byte[] dstbuf, int dstoff, int cnt) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
index 6fff656..45202b5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
@@ -45,23 +45,20 @@
package org.eclipse.jgit.internal.storage.dfs;
import java.io.IOException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.concurrent.locks.ReentrantLock;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.internal.JGitText;
/**
- * Caches slices of a {@link DfsPackFile} in memory for faster read access.
+ * Caches slices of a {@link BlockBasedFile} in memory for faster read access.
* <p>
* The DfsBlockCache serves as a Java based "buffer cache", loading segments of
- * a DfsPackFile into the JVM heap prior to use. As JGit often wants to do reads
- * of only tiny slices of a file, the DfsBlockCache tries to smooth out these
- * tiny reads into larger block-sized IO operations.
+ * a BlockBasedFile into the JVM heap prior to use. As JGit often wants to do
+ * reads of only tiny slices of a file, the DfsBlockCache tries to smooth out
+ * these tiny reads into larger block-sized IO operations.
* <p>
* Whenever a cache miss occurs, loading is invoked by exactly one thread for
* the given <code>(DfsPackKey,position)</code> key tuple. This is ensured by an
@@ -108,14 +105,7 @@ public final class DfsBlockCache {
* settings, usually too low of a limit.
*/
public static void reconfigure(DfsBlockCacheConfig cfg) {
- DfsBlockCache nc = new DfsBlockCache(cfg);
- DfsBlockCache oc = cache;
- cache = nc;
-
- if (oc != null) {
- for (DfsPackFile pack : oc.getPackFiles())
- pack.key.cachedSize.set(0);
- }
+ cache = new DfsBlockCache(cfg);
}
/** @return the currently active DfsBlockCache. */
@@ -153,12 +143,6 @@ public static DfsBlockCache getInstance() {
/** As {@link #blockSize} is a power of 2, bits to shift for a / blockSize. */
private final int blockSizeShift;
- /** Cache of pack files, indexed by description. */
- private final Map<DfsPackDescription, DfsPackFile> packCache;
-
- /** View of pack files in the pack cache. */
- private final Collection<DfsPackFile> packFiles;
-
/** Number of times a block was found in the cache. */
private final AtomicLong statHit;
@@ -194,13 +178,12 @@ private DfsBlockCache(final DfsBlockCacheConfig cfg) {
blockSizeShift = Integer.numberOfTrailingZeros(blockSize);
clockLock = new ReentrantLock(true /* fair */);
- clockHand = new Ref<>(new DfsPackKey(), -1, 0, null);
+ String none = ""; //$NON-NLS-1$
+ clockHand = new Ref<>(
+ DfsStreamKey.of(new DfsRepositoryDescription(none), none),
+ -1, 0, null);
clockHand.next = clockHand;
- packCache = new ConcurrentHashMap<>(
- 16, 0.75f, 1);
- packFiles = Collections.unmodifiableCollection(packCache.values());
-
statHit = new AtomicLong();
statMiss = new AtomicLong();
}
@@ -249,38 +232,6 @@ public long getEvictions() {
return statEvict;
}
- /**
- * Get the pack files stored in this cache.
- *
- * @return a collection of pack files, some of which may not actually be
- * present; the caller should check the pack's cached size.
- */
- public Collection<DfsPackFile> getPackFiles() {
- return packFiles;
- }
-
- DfsPackFile getOrCreate(DfsPackDescription dsc, DfsPackKey key) {
- // TODO This table grows without bound. It needs to clean up
- // entries that aren't in cache anymore, and aren't being used
- // by a live DfsObjDatabase reference.
-
- DfsPackFile pack = packCache.get(dsc);
- if (pack != null && !pack.invalid()) {
- return pack;
- }
-
- // 'pack' either didn't exist or was invalid. Compute a new
- // entry atomically (guaranteed by ConcurrentHashMap).
- return packCache.compute(dsc, (k, v) -> {
- if (v != null && !v.invalid()) { // valid value added by
- return v; // another thread
- } else {
- return new DfsPackFile(
- this, dsc, key != null ? key : new DfsPackKey());
- }
- });
- }
-
private int hash(int packHash, long off) {
return packHash + (int) (off >>> blockSizeShift);
}
@@ -302,26 +253,28 @@ private static int tableSize(final DfsBlockCacheConfig cfg) {
/**
* Lookup a cached object, creating and loading it if it doesn't exist.
*
- * @param pack
+ * @param file
* the pack that "contains" the cached object.
* @param position
* offset within <code>pack</code> of the object.
* @param ctx
* current thread's reader.
+ * @param fileChannel
+ * optional channel to read {@code pack}.
* @return the object reference.
* @throws IOException
* the reference was not in the cache and could not be loaded.
*/
- DfsBlock getOrLoad(DfsPackFile pack, long position, DfsReader ctx)
- throws IOException {
+ DfsBlock getOrLoad(BlockBasedFile file, long position, DfsReader ctx,
+ @Nullable ReadableChannel fileChannel) throws IOException {
final long requestedPosition = position;
- position = pack.alignToBlock(position);
+ position = file.alignToBlock(position);
- DfsPackKey key = pack.key;
+ DfsStreamKey key = file.key;
int slot = slot(key, position);
HashEntry e1 = table.get(slot);
DfsBlock v = scan(e1, key, position);
- if (v != null) {
+ if (v != null && v.contains(key, requestedPosition)) {
ctx.stats.blockCacheHit++;
statHit.incrementAndGet();
return v;
@@ -345,7 +298,7 @@ DfsBlock getOrLoad(DfsPackFile pack, long position, DfsReader ctx)
statMiss.incrementAndGet();
boolean credit = true;
try {
- v = pack.readOneBlock(position, ctx);
+ v = file.readOneBlock(requestedPosition, ctx, fileChannel);
credit = false;
} finally {
if (credit)
@@ -358,7 +311,6 @@ DfsBlock getOrLoad(DfsPackFile pack, long position, DfsReader ctx)
e2 = table.get(slot);
}
- key.cachedSize.addAndGet(v.size());
Ref<DfsBlock> ref = new Ref<>(key, position, v.size(), v);
ref.hot = true;
for (;;) {
@@ -374,9 +326,9 @@ DfsBlock getOrLoad(DfsPackFile pack, long position, DfsReader ctx)
// If the block size changed from the default, it is possible the block
// that was loaded is the wrong block for the requested position.
- if (v.contains(pack.key, requestedPosition))
+ if (v.contains(file.key, requestedPosition))
return v;
- return getOrLoad(pack, requestedPosition, ctx);
+ return getOrLoad(file, requestedPosition, ctx, fileChannel);
}
@SuppressWarnings("unchecked")
@@ -406,7 +358,6 @@ private void reserveSpace(int reserve) {
dead.next = null;
dead.value = null;
live -= dead.size;
- dead.pack.cachedSize.addAndGet(-dead.size);
statEvict++;
} while (maxBytes < live);
clockHand = prev;
@@ -439,10 +390,14 @@ private void addToClock(Ref ref, int credit) {
}
void put(DfsBlock v) {
- put(v.pack, v.start, v.size(), v);
+ put(v.stream, v.start, v.size(), v);
}
- <T> Ref<T> put(DfsPackKey key, long pos, int size, T v) {
+ <T> Ref<T> putRef(DfsStreamKey key, long size, T v) {
+ return put(key, 0, (int) Math.min(size, Integer.MAX_VALUE), v);
+ }
+
+ <T> Ref<T> put(DfsStreamKey key, long pos, int size, T v) {
int slot = slot(key, pos);
HashEntry e1 = table.get(slot);
Ref<T> ref = scanRef(e1, key, pos);
@@ -462,7 +417,6 @@ <T> Ref<T> put(DfsPackKey key, long pos, int size, T v) {
}
}
- key.cachedSize.addAndGet(size);
ref = new Ref<>(key, pos, size, v);
ref.hot = true;
for (;;) {
@@ -478,12 +432,12 @@ <T> Ref<T> put(DfsPackKey key, long pos, int size, T v) {
return ref;
}
- boolean contains(DfsPackKey key, long position) {
+ boolean contains(DfsStreamKey key, long position) {
return scan(table.get(slot(key, position)), key, position) != null;
}
@SuppressWarnings("unchecked")
- <T> T get(DfsPackKey key, long position) {
+ <T> T get(DfsStreamKey key, long position) {
T val = (T) scan(table.get(slot(key, position)), key, position);
if (val == null)
statMiss.incrementAndGet();
@@ -492,31 +446,36 @@ <T> T get(DfsPackKey key, long position) {
return val;
}
- private <T> T scan(HashEntry n, DfsPackKey pack, long position) {
- Ref<T> r = scanRef(n, pack, position);
+ private <T> T scan(HashEntry n, DfsStreamKey key, long position) {
+ Ref<T> r = scanRef(n, key, position);
return r != null ? r.get() : null;
}
+ <T> Ref<T> getRef(DfsStreamKey key) {
+ Ref<T> r = scanRef(table.get(slot(key, 0)), key, 0);
+ if (r != null)
+ statHit.incrementAndGet();
+ else
+ statMiss.incrementAndGet();
+ return r;
+ }
+
@SuppressWarnings("unchecked")
- private <T> Ref<T> scanRef(HashEntry n, DfsPackKey pack, long position) {
+ private <T> Ref<T> scanRef(HashEntry n, DfsStreamKey key, long position) {
for (; n != null; n = n.next) {
Ref<T> r = n.ref;
- if (r.pack == pack && r.position == position)
+ if (r.position == position && r.key.equals(key))
return r.get() != null ? r : null;
}
return null;
}
- void remove(DfsPackFile pack) {
- packCache.remove(pack.getPackDescription());
+ private int slot(DfsStreamKey key, long position) {
+ return (hash(key.hash, position) >>> 1) % tableSize;
}
- private int slot(DfsPackKey pack, long position) {
- return (hash(pack.hash, position) >>> 1) % tableSize;
- }
-
- private ReentrantLock lockFor(DfsPackKey pack, long position) {
- return loadLocks[(hash(pack.hash, position) >>> 1) % loadLocks.length];
+ private ReentrantLock lockFor(DfsStreamKey key, long position) {
+ return loadLocks[(hash(key.hash, position) >>> 1) % loadLocks.length];
}
private static HashEntry clean(HashEntry top) {
@@ -542,15 +501,15 @@ private static final class HashEntry {
}
static final class Ref<T> {
- final DfsPackKey pack;
+ final DfsStreamKey key;
final long position;
final int size;
volatile T value;
Ref next;
volatile boolean hot;
- Ref(DfsPackKey pack, long position, int size, T v) {
- this.pack = pack;
+ Ref(DfsStreamKey key, long position, int size, T v) {
+ this.key = key;
this.position = position;
this.size = size;
this.value = v;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java
new file mode 100644
index 0000000..75eade2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsFsck.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.CorruptPackIndexException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.fsck.FsckError;
+import org.eclipse.jgit.internal.fsck.FsckError.CorruptIndex;
+import org.eclipse.jgit.internal.fsck.FsckPackParser;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectChecker;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.ObjectWalk;
+import org.eclipse.jgit.revwalk.RevObject;
+
+/** Verify the validity and connectivity of a DFS repository. */
+public class DfsFsck {
+ private final DfsRepository repo;
+ private final DfsObjDatabase objdb;
+ private ObjectChecker objChecker = new ObjectChecker();
+
+ /**
+ * Initialize DFS fsck.
+ *
+ * @param repository
+ * the dfs repository to check.
+ */
+ public DfsFsck(DfsRepository repository) {
+ repo = repository;
+ objdb = repo.getObjectDatabase();
+ }
+
+ /**
+ * Verify the integrity and connectivity of all objects in the object
+ * database.
+ *
+ * @param pm
+ * callback to provide progress feedback during the check.
+ * @return all errors about the repository.
+ * @throws IOException
+ * if encounters IO errors during the process.
+ */
+ public FsckError check(ProgressMonitor pm) throws IOException {
+ if (pm == null) {
+ pm = NullProgressMonitor.INSTANCE;
+ }
+
+ FsckError errors = new FsckError();
+ checkPacks(pm, errors);
+ checkConnectivity(pm, errors);
+ return errors;
+ }
+
+ private void checkPacks(ProgressMonitor pm, FsckError errors)
+ throws IOException, FileNotFoundException {
+ try (DfsReader ctx = objdb.newReader()) {
+ for (DfsPackFile pack : objdb.getPacks()) {
+ DfsPackDescription packDesc = pack.getPackDescription();
+ try (ReadableChannel rc = objdb.openFile(packDesc, PACK)) {
+ verifyPack(pm, errors, ctx, pack, rc);
+ } catch (MissingObjectException e) {
+ errors.getMissingObjects().add(e.getObjectId());
+ } catch (CorruptPackIndexException e) {
+ errors.getCorruptIndices().add(new CorruptIndex(
+ pack.getPackDescription().getFileName(INDEX),
+ e.getErrorType()));
+ }
+ }
+ }
+ }
+
+ private void verifyPack(ProgressMonitor pm, FsckError errors, DfsReader ctx,
+ DfsPackFile pack, ReadableChannel ch)
+ throws IOException, CorruptPackIndexException {
+ FsckPackParser fpp = new FsckPackParser(objdb, ch);
+ fpp.setObjectChecker(objChecker);
+ fpp.overwriteObjectCount(pack.getPackDescription().getObjectCount());
+ fpp.parse(pm);
+ errors.getCorruptObjects().addAll(fpp.getCorruptObjects());
+
+ fpp.verifyIndex(pack.getPackIndex(ctx));
+ }
+
+ private void checkConnectivity(ProgressMonitor pm, FsckError errors)
+ throws IOException {
+ pm.beginTask(JGitText.get().countingObjects, ProgressMonitor.UNKNOWN);
+ try (ObjectWalk ow = new ObjectWalk(repo)) {
+ for (Ref r : repo.getAllRefs().values()) {
+ RevObject tip;
+ try {
+ tip = ow.parseAny(r.getObjectId());
+ if (r.getLeaf().getName().startsWith(Constants.R_HEADS)
+ && tip.getType() != Constants.OBJ_COMMIT) {
+ // heads should only point to a commit object
+ errors.getNonCommitHeads().add(r.getLeaf().getName());
+ }
+ } catch (MissingObjectException e) {
+ errors.getMissingObjects().add(e.getObjectId());
+ continue;
+ }
+ ow.markStart(tip);
+ }
+ try {
+ ow.checkConnectivity();
+ } catch (MissingObjectException e) {
+ errors.getMissingObjects().add(e.getObjectId());
+ }
+ }
+ pm.endTask();
+ }
+
+ /**
+ * Use a customized object checker instead of the default one. Caller can
+ * specify a skip list to ignore some errors.
+ *
+ * @param objChecker
+ * A customized object checker.
+ */
+ public void setObjectChecker(ObjectChecker objChecker) {
+ this.objChecker = objChecker;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
index 55f9cc2..304a931 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
@@ -50,13 +50,16 @@
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.INSERT;
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.RECEIVE;
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
+import static org.eclipse.jgit.internal.storage.dfs.DfsPackCompactor.configureReftable;
import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
import static org.eclipse.jgit.internal.storage.pack.PackWriter.NONE;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.EnumSet;
@@ -72,6 +75,9 @@
import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.internal.storage.pack.PackWriter;
+import org.eclipse.jgit.internal.storage.reftable.ReftableCompactor;
+import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
+import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
import org.eclipse.jgit.internal.storage.reftree.RefTreeNames;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
@@ -94,14 +100,16 @@ public class DfsGarbageCollector {
private final DfsObjDatabase objdb;
private final List<DfsPackDescription> newPackDesc;
-
private final List<PackStatistics> newPackStats;
-
private final List<ObjectIdSet> newPackObj;
private DfsReader ctx;
private PackConfig packConfig;
+ private ReftableConfig reftableConfig;
+ private boolean convertToReftable = true;
+ private long reftableInitialMinUpdateIndex = 1;
+ private long reftableInitialMaxUpdateIndex = 1;
// See packIsCoalesceableGarbage(), below, for how these two variables
// interact.
@@ -110,8 +118,10 @@ public class DfsGarbageCollector {
private long startTimeMillis;
private List<DfsPackFile> packsBefore;
+ private List<DfsReftable> reftablesBefore;
private List<DfsPackFile> expiredGarbagePacks;
+ private Collection<Ref> refsBefore;
private Set<ObjectId> allHeadsAndTags;
private Set<ObjectId> allTags;
private Set<ObjectId> nonHeads;
@@ -151,6 +161,60 @@ public DfsGarbageCollector setPackConfig(PackConfig newConfig) {
return this;
}
+ /**
+ * @param cfg
+ * configuration to write a reftable. Reftable writing is
+ * disabled (default) when {@code cfg} is {@code null}.
+ * @return {@code this}
+ */
+ public DfsGarbageCollector setReftableConfig(ReftableConfig cfg) {
+ reftableConfig = cfg;
+ return this;
+ }
+
+ /**
+ * @param convert
+ * if true, {@link #setReftableConfig(ReftableConfig)} has been
+ * set non-null, and a GC reftable doesn't yet exist, the garbage
+ * collector will make one by scanning the existing references,
+ * and writing a new reftable. Default is {@code true}.
+ * @return {@code this}
+ */
+ public DfsGarbageCollector setConvertToReftable(boolean convert) {
+ convertToReftable = convert;
+ return this;
+ }
+
+ /**
+ * Set minUpdateIndex for the initial reftable created during conversion.
+ *
+ * @param u
+ * minUpdateIndex for the initial reftable created by scanning
+ * {@link DfsRefDatabase#getRefs(String)}. Ignored unless caller
+ * has also set {@link #setReftableConfig(ReftableConfig)}.
+ * Defaults to {@code 1}. Must be {@code u >= 0}.
+ * @return {@code this}
+ */
+ public DfsGarbageCollector setReftableInitialMinUpdateIndex(long u) {
+ reftableInitialMinUpdateIndex = Math.max(u, 0);
+ return this;
+ }
+
+ /**
+ * Set maxUpdateIndex for the initial reftable created during conversion.
+ *
+ * @param u
+ * maxUpdateIndex for the initial reftable created by scanning
+ * {@link DfsRefDatabase#getRefs(String)}. Ignored unless caller
+ * has also set {@link #setReftableConfig(ReftableConfig)}.
+ * Defaults to {@code 1}. Must be {@code u >= 0}.
+ * @return {@code this}
+ */
+ public DfsGarbageCollector setReftableInitialMaxUpdateIndex(long u) {
+ reftableInitialMaxUpdateIndex = Math.max(0, u);
+ return this;
+ }
+
/** @return garbage packs smaller than this size will be repacked. */
public long getCoalesceGarbageLimit() {
return coalesceGarbageLimit;
@@ -240,8 +304,9 @@ public boolean pack(ProgressMonitor pm) throws IOException {
refdb.refresh();
objdb.clearCache();
- Collection<Ref> refsBefore = getAllRefs();
+ refsBefore = getAllRefs();
readPacksBefore();
+ readReftablesBefore();
Set<ObjectId> allHeads = new HashSet<>();
allHeadsAndTags = new HashSet<>();
@@ -274,6 +339,12 @@ public boolean pack(ProgressMonitor pm) throws IOException {
// Hoist all branch tips and tags earlier in the pack file
tagTargets.addAll(allHeadsAndTags);
+ // Combine the GC_REST objects into the GC pack if requested
+ if (packConfig.getSinglePack()) {
+ allHeadsAndTags.addAll(nonHeads);
+ nonHeads.clear();
+ }
+
boolean rollback = true;
try {
packHeads(pm);
@@ -327,6 +398,11 @@ private void readPacksBefore() throws IOException {
}
}
+ private void readReftablesBefore() throws IOException {
+ DfsReftable[] tables = objdb.getReftables();
+ reftablesBefore = new ArrayList<>(Arrays.asList(tables));
+ }
+
private boolean packIsExpiredGarbage(DfsPackDescription d, long now) {
// Consider the garbage pack as expired when it's older than
// garbagePackTtl. This check gives concurrent inserter threads
@@ -401,7 +477,7 @@ private static long dayStartInMillis(long timeInMillis) {
}
/** @return all of the source packs that fed into this compaction. */
- public List<DfsPackDescription> getSourcePacks() {
+ public Set<DfsPackDescription> getSourcePacks() {
return toPrune();
}
@@ -415,28 +491,37 @@ public List<PackStatistics> getNewPackStatistics() {
return newPackStats;
}
- private List<DfsPackDescription> toPrune() {
- int cnt = packsBefore.size();
- List<DfsPackDescription> all = new ArrayList<>(cnt);
+ private Set<DfsPackDescription> toPrune() {
+ Set<DfsPackDescription> toPrune = new HashSet<>();
for (DfsPackFile pack : packsBefore) {
- all.add(pack.getPackDescription());
+ toPrune.add(pack.getPackDescription());
+ }
+ if (reftableConfig != null) {
+ for (DfsReftable table : reftablesBefore) {
+ toPrune.add(table.getPackDescription());
+ }
}
for (DfsPackFile pack : expiredGarbagePacks) {
- all.add(pack.getPackDescription());
+ toPrune.add(pack.getPackDescription());
}
- return all;
+ return toPrune;
}
private void packHeads(ProgressMonitor pm) throws IOException {
- if (allHeadsAndTags.isEmpty())
+ if (allHeadsAndTags.isEmpty()) {
+ writeReftable();
return;
+ }
try (PackWriter pw = newPackWriter()) {
pw.setTagTargets(tagTargets);
pw.preparePack(pm, allHeadsAndTags, NONE, NONE, allTags);
- if (0 < pw.getObjectCount())
- writePack(GC, pw, pm,
- estimateGcPackSize(INSERT, RECEIVE, COMPACT, GC));
+ if (0 < pw.getObjectCount()) {
+ long estSize = estimateGcPackSize(INSERT, RECEIVE, COMPACT, GC);
+ writePack(GC, pw, pm, estSize);
+ } else {
+ writeReftable();
+ }
}
}
@@ -554,25 +639,32 @@ private DfsPackDescription writePack(PackSource source, PackWriter pw,
estimatedPackSize);
newPackDesc.add(pack);
+ if (source == GC && reftableConfig != null) {
+ writeReftable(pack);
+ }
+
try (DfsOutputStream out = objdb.writeFile(pack, PACK)) {
pw.writePack(pm, pm, out);
pack.addFileExt(PACK);
+ pack.setBlockSize(PACK, out.blockSize());
}
- try (CountingOutputStream cnt =
- new CountingOutputStream(objdb.writeFile(pack, INDEX))) {
+ try (DfsOutputStream out = objdb.writeFile(pack, INDEX)) {
+ CountingOutputStream cnt = new CountingOutputStream(out);
pw.writeIndex(cnt);
pack.addFileExt(INDEX);
pack.setFileSize(INDEX, cnt.getCount());
+ pack.setBlockSize(INDEX, out.blockSize());
pack.setIndexVersion(pw.getIndexVersion());
}
if (pw.prepareBitmapIndex(pm)) {
- try (CountingOutputStream cnt = new CountingOutputStream(
- objdb.writeFile(pack, BITMAP_INDEX))) {
+ try (DfsOutputStream out = objdb.writeFile(pack, BITMAP_INDEX)) {
+ CountingOutputStream cnt = new CountingOutputStream(out);
pw.writeBitmapIndex(cnt);
pack.addFileExt(BITMAP_INDEX);
pack.setFileSize(BITMAP_INDEX, cnt.getCount());
+ pack.setBlockSize(BITMAP_INDEX, out.blockSize());
}
}
@@ -581,8 +673,62 @@ private DfsPackDescription writePack(PackSource source, PackWriter pw,
pack.setLastModified(startTimeMillis);
newPackStats.add(stats);
newPackObj.add(pw.getObjectSet());
-
- DfsBlockCache.getInstance().getOrCreate(pack, null);
return pack;
}
+
+ private void writeReftable() throws IOException {
+ if (reftableConfig != null) {
+ DfsPackDescription pack = objdb.newPack(GC);
+ newPackDesc.add(pack);
+ writeReftable(pack);
+ }
+ }
+
+ private void writeReftable(DfsPackDescription pack) throws IOException {
+ if (convertToReftable && !hasGcReftable()) {
+ writeReftable(pack, refsBefore);
+ return;
+ }
+
+ try (ReftableStack stack = ReftableStack.open(ctx, reftablesBefore)) {
+ ReftableCompactor compact = new ReftableCompactor();
+ compact.addAll(stack.readers());
+ compact.setIncludeDeletes(false);
+ compactReftable(pack, compact);
+ }
+ }
+
+ private boolean hasGcReftable() {
+ for (DfsReftable table : reftablesBefore) {
+ if (table.getPackDescription().getPackSource() == GC) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void writeReftable(DfsPackDescription pack, Collection<Ref> refs)
+ throws IOException {
+ try (DfsOutputStream out = objdb.writeFile(pack, REFTABLE)) {
+ ReftableConfig cfg = configureReftable(reftableConfig, out);
+ ReftableWriter writer = new ReftableWriter(cfg)
+ .setMinUpdateIndex(reftableInitialMinUpdateIndex)
+ .setMaxUpdateIndex(reftableInitialMaxUpdateIndex)
+ .begin(out)
+ .sortAndWriteRefs(refs)
+ .finish();
+ pack.addFileExt(REFTABLE);
+ pack.setReftableStats(writer.getStats());
+ }
+ }
+
+ private void compactReftable(DfsPackDescription pack,
+ ReftableCompactor compact) throws IOException {
+ try (DfsOutputStream out = objdb.writeFile(pack, REFTABLE)) {
+ compact.setConfig(configureReftable(reftableConfig, out));
+ compact.compact(out);
+ pack.addFileExt(REFTABLE);
+ pack.setReftableStats(compact.getStats());
+ }
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
index e65c9fd..19e8652 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
@@ -104,7 +104,7 @@ public class DfsInserter extends ObjectInserter {
ObjectIdOwnerMap<PackedObjectInfo> objectMap;
DfsBlockCache cache;
- DfsPackKey packKey;
+ DfsStreamKey packKey;
DfsPackDescription packDsc;
PackStream packOut;
private boolean rollback;
@@ -221,7 +221,7 @@ public void flush() throws IOException {
db.commitPack(Collections.singletonList(packDsc), null);
rollback = false;
- DfsPackFile p = cache.getOrCreate(packDsc, packKey);
+ DfsPackFile p = new DfsPackFile(cache, packDsc);
if (index != null)
p.setPackIndex(index);
db.addPack(p);
@@ -281,8 +281,10 @@ private void beginPack() throws IOException {
rollback = true;
packDsc = db.newPack(DfsObjDatabase.PackSource.INSERT);
- packOut = new PackStream(db.writeFile(packDsc, PACK));
- packKey = new DfsPackKey();
+ DfsOutputStream dfsOut = db.writeFile(packDsc, PACK);
+ packDsc.setBlockSize(PACK, dfsOut.blockSize());
+ packOut = new PackStream(dfsOut);
+ packKey = packDsc.getStreamKey(PACK);
// Write the header as though it were a single object pack.
byte[] buf = packOut.hdrBuf;
@@ -312,13 +314,14 @@ PackIndex writePackIndex(DfsPackDescription pack, byte[] packHash,
packIndex = PackIndex.read(buf.openInputStream());
}
- DfsOutputStream os = db.writeFile(pack, INDEX);
- try (CountingOutputStream cnt = new CountingOutputStream(os)) {
+ try (DfsOutputStream os = db.writeFile(pack, INDEX)) {
+ CountingOutputStream cnt = new CountingOutputStream(os);
if (buf != null)
buf.writeTo(cnt, null);
else
index(cnt, packHash, list);
pack.addFileExt(INDEX);
+ pack.setBlockSize(INDEX, os.blockSize());
pack.setFileSize(INDEX, cnt.getCount());
} finally {
if (buf != null) {
@@ -497,7 +500,7 @@ private int setInput(long pos, Inflater inf)
inf.setInput(currBuf, s, n);
return n;
}
- throw new EOFException(DfsText.get().unexpectedEofInPack);
+ throw new EOFException(JGitText.get().unexpectedEofInPack);
}
private DfsBlock getOrLoadBlock(long pos) throws IOException {
@@ -510,7 +513,7 @@ private DfsBlock getOrLoadBlock(long pos) throws IOException {
for (int p = 0; p < blockSize;) {
int n = out.read(s + p, ByteBuffer.wrap(d, p, blockSize - p));
if (n <= 0)
- throw new EOFException(DfsText.get().unexpectedEofInPack);
+ throw new EOFException(JGitText.get().unexpectedEofInPack);
p += n;
}
b = new DfsBlock(packKey, s, d);
@@ -566,13 +569,13 @@ public ObjectLoader open(AnyObjectId objectId, int typeHint)
byte[] buf = buffer();
int cnt = packOut.read(obj.getOffset(), buf, 0, 20);
if (cnt <= 0)
- throw new EOFException(DfsText.get().unexpectedEofInPack);
+ throw new EOFException(JGitText.get().unexpectedEofInPack);
int c = buf[0] & 0xff;
int type = (c >> 4) & 7;
if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA)
throw new IOException(MessageFormat.format(
- DfsText.get().cannotReadBackDelta, Integer.toString(type)));
+ JGitText.get().cannotReadBackDelta, Integer.toString(type)));
if (typeHint != OBJ_ANY && type != typeHint) {
throw new IncorrectObjectTypeException(objectId.copy(), typeHint);
}
@@ -582,7 +585,7 @@ public ObjectLoader open(AnyObjectId objectId, int typeHint)
int shift = 4;
while ((c & 0x80) != 0) {
if (ptr >= cnt)
- throw new EOFException(DfsText.get().unexpectedEofInPack);
+ throw new EOFException(JGitText.get().unexpectedEofInPack);
c = buf[ptr++] & 0xff;
sz += ((long) (c & 0x7f)) << shift;
shift += 7;
@@ -633,11 +636,11 @@ private class StreamLoader extends ObjectLoader {
private final int type;
private final long size;
- private final DfsPackKey srcPack;
+ private final DfsStreamKey srcPack;
private final long pos;
StreamLoader(ObjectId id, int type, long sz,
- DfsPackKey key, long pos) {
+ DfsStreamKey key, long pos) {
this.id = id;
this.type = type;
this.size = sz;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java
index 32ee6c2..9439822 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java
@@ -48,6 +48,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -61,7 +62,9 @@
/** Manages objects stored in {@link DfsPackFile} on a storage system. */
public abstract class DfsObjDatabase extends ObjectDatabase {
- private static final PackList NO_PACKS = new PackList(new DfsPackFile[0]) {
+ private static final PackList NO_PACKS = new PackList(
+ new DfsPackFile[0],
+ new DfsReftable[0]) {
@Override
boolean dirty() {
return true;
@@ -192,6 +195,18 @@ public ObjectInserter newInserter() {
}
/**
+ * Scan and list all available reftable files in the repository.
+ *
+ * @return list of available reftables. The returned array is shared with
+ * the implementation and must not be modified by the caller.
+ * @throws IOException
+ * the pack list cannot be initialized.
+ */
+ public DfsReftable[] getReftables() throws IOException {
+ return getPackList().reftables;
+ }
+
+ /**
* Scan and list all available pack files in the repository.
*
* @return list of available packs, with some additional metadata. The
@@ -220,6 +235,16 @@ protected DfsRepository getRepository() {
}
/**
+ * List currently known reftable files in the repository, without scanning.
+ *
+ * @return list of available reftables. The returned array is shared with
+ * the implementation and must not be modified by the caller.
+ */
+ public DfsReftable[] getCurrentReftables() {
+ return getCurrentPackList().reftables;
+ }
+
+ /**
* List currently known pack files in the repository, without scanning.
*
* @return list of available packs, with some additional metadata. The
@@ -428,7 +453,7 @@ void addPack(DfsPackFile newPack) throws IOException {
DfsPackFile[] packs = new DfsPackFile[1 + o.packs.length];
packs[0] = newPack;
System.arraycopy(o.packs, 0, packs, 1, o.packs.length);
- n = new PackListImpl(packs);
+ n = new PackListImpl(packs, o.reftables);
} while (!packList.compareAndSet(o, n));
}
@@ -454,60 +479,93 @@ PackList scanPacks(final PackList original) throws IOException {
private PackList scanPacksImpl(PackList old) throws IOException {
DfsBlockCache cache = DfsBlockCache.getInstance();
- Map<DfsPackDescription, DfsPackFile> forReuse = reuseMap(old);
+ Map<DfsPackDescription, DfsPackFile> packs = packMap(old);
+ Map<DfsPackDescription, DfsReftable> reftables = reftableMap(old);
+
List<DfsPackDescription> scanned = listPacks();
Collections.sort(scanned);
- List<DfsPackFile> list = new ArrayList<>(scanned.size());
+ List<DfsPackFile> newPacks = new ArrayList<>(scanned.size());
+ List<DfsReftable> newReftables = new ArrayList<>(scanned.size());
boolean foundNew = false;
for (DfsPackDescription dsc : scanned) {
- DfsPackFile oldPack = forReuse.remove(dsc);
+ DfsPackFile oldPack = packs.remove(dsc);
if (oldPack != null) {
- list.add(oldPack);
- } else {
- list.add(cache.getOrCreate(dsc, null));
+ newPacks.add(oldPack);
+ } else if (dsc.hasFileExt(PackExt.PACK)) {
+ newPacks.add(new DfsPackFile(cache, dsc));
+ foundNew = true;
+ }
+
+ DfsReftable oldReftable = reftables.remove(dsc);
+ if (oldReftable != null) {
+ newReftables.add(oldReftable);
+ } else if (dsc.hasFileExt(PackExt.REFTABLE)) {
+ newReftables.add(new DfsReftable(cache, dsc));
foundNew = true;
}
}
- for (DfsPackFile p : forReuse.values())
- p.close();
- if (list.isEmpty())
- return new PackListImpl(NO_PACKS.packs);
+ if (newPacks.isEmpty())
+ return new PackListImpl(NO_PACKS.packs, NO_PACKS.reftables);
if (!foundNew) {
old.clearDirty();
return old;
}
- return new PackListImpl(list.toArray(new DfsPackFile[list.size()]));
+ Collections.sort(newReftables, reftableComparator());
+ return new PackListImpl(
+ newPacks.toArray(new DfsPackFile[0]),
+ newReftables.toArray(new DfsReftable[0]));
}
- private static Map<DfsPackDescription, DfsPackFile> reuseMap(PackList old) {
- Map<DfsPackDescription, DfsPackFile> forReuse
- = new HashMap<>();
+ private static Map<DfsPackDescription, DfsPackFile> packMap(PackList old) {
+ Map<DfsPackDescription, DfsPackFile> forReuse = new HashMap<>();
for (DfsPackFile p : old.packs) {
- if (p.invalid()) {
- // The pack instance is corrupted, and cannot be safely used
- // again. Do not include it in our reuse map.
- //
- p.close();
- continue;
- }
-
- DfsPackFile prior = forReuse.put(p.getPackDescription(), p);
- if (prior != null) {
- // This should never occur. It should be impossible for us
- // to have two pack files with the same name, as all of them
- // came out of the same directory. If it does, we promised to
- // close any PackFiles we did not reuse, so close the second,
- // readers are likely to be actively using the first.
- //
- forReuse.put(prior.getPackDescription(), prior);
- p.close();
+ if (!p.invalid()) {
+ forReuse.put(p.desc, p);
}
}
return forReuse;
}
+ private static Map<DfsPackDescription, DfsReftable> reftableMap(PackList old) {
+ Map<DfsPackDescription, DfsReftable> forReuse = new HashMap<>();
+ for (DfsReftable p : old.reftables) {
+ if (!p.invalid()) {
+ forReuse.put(p.desc, p);
+ }
+ }
+ return forReuse;
+ }
+
+ /** @return comparator to sort {@link DfsReftable} by priority. */
+ protected Comparator<DfsReftable> reftableComparator() {
+ return (fa, fb) -> {
+ DfsPackDescription a = fa.getPackDescription();
+ DfsPackDescription b = fb.getPackDescription();
+
+ // GC, COMPACT reftables first by higher category.
+ int c = category(b) - category(a);
+ if (c != 0) {
+ return c;
+ }
+
+ // Lower maxUpdateIndex first.
+ c = Long.signum(a.getMaxUpdateIndex() - b.getMaxUpdateIndex());
+ if (c != 0) {
+ return c;
+ }
+
+ // Older reftable first.
+ return Long.signum(a.getLastModified() - b.getLastModified());
+ };
+ }
+
+ static int category(DfsPackDescription d) {
+ PackSource s = d.getPackSource();
+ return s != null ? s.category : 0;
+ }
+
/** Clears the cached list of packs, forcing them to be scanned again. */
protected void clearCache() {
packList.set(NO_PACKS);
@@ -515,12 +573,7 @@ protected void clearCache() {
@Override
public void close() {
- // PackList packs = packList.get();
packList.set(NO_PACKS);
-
- // TODO Close packs if they aren't cached.
- // for (DfsPackFile p : packs.packs)
- // p.close();
}
/** Snapshot of packs scanned in a single pass. */
@@ -528,10 +581,14 @@ public static abstract class PackList {
/** All known packs, sorted. */
public final DfsPackFile[] packs;
+ /** All known reftables, sorted. */
+ public final DfsReftable[] reftables;
+
private long lastModified = -1;
- PackList(DfsPackFile[] packs) {
+ PackList(DfsPackFile[] packs, DfsReftable[] reftables) {
this.packs = packs;
+ this.reftables = reftables;
}
/** @return last modified time of all packs, in milliseconds. */
@@ -562,8 +619,8 @@ public long getLastModified() {
private static final class PackListImpl extends PackList {
private volatile boolean dirty;
- PackListImpl(DfsPackFile[] packs) {
- super(packs);
+ PackListImpl(DfsPackFile[] packs, DfsReftable[] reftables) {
+ super(packs, reftables);
}
@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
index f7c87a4..99663eb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
@@ -44,21 +44,29 @@
package org.eclipse.jgit.internal.storage.dfs;
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.COMPACT;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC;
import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
import static org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation.PACK_DELTA;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
+import java.util.Set;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.file.PackIndex;
import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
import org.eclipse.jgit.internal.storage.pack.PackWriter;
+import org.eclipse.jgit.internal.storage.reftable.ReftableCompactor;
+import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
@@ -89,16 +97,15 @@
*/
public class DfsPackCompactor {
private final DfsRepository repo;
-
private final List<DfsPackFile> srcPacks;
-
+ private final List<DfsReftable> srcReftables;
private final List<ObjectIdSet> exclude;
- private final List<DfsPackDescription> newPacks;
-
- private final List<PackStatistics> newStats;
+ private PackStatistics newStats;
+ private DfsPackDescription outDesc;
private int autoAddSize;
+ private ReftableConfig reftableConfig;
private RevWalk rw;
private RevFlag added;
@@ -114,9 +121,19 @@ public DfsPackCompactor(DfsRepository repository) {
repo = repository;
autoAddSize = 5 * 1024 * 1024; // 5 MiB
srcPacks = new ArrayList<>();
+ srcReftables = new ArrayList<>();
exclude = new ArrayList<>(4);
- newPacks = new ArrayList<>(1);
- newStats = new ArrayList<>(1);
+ }
+
+ /**
+ * @param cfg
+ * configuration to write a reftable. Reftable compacting is
+ * disabled (default) when {@code cfg} is {@code null}.
+ * @return {@code this}
+ */
+ public DfsPackCompactor setReftableConfig(ReftableConfig cfg) {
+ reftableConfig = cfg;
+ return this;
}
/**
@@ -137,7 +154,19 @@ public DfsPackCompactor add(DfsPackFile pack) {
}
/**
- * Automatically select packs to be included, and add them.
+ * Add a reftable to be compacted.
+ *
+ * @param table
+ * a reftable to combine.
+ * @return {@code this}
+ */
+ public DfsPackCompactor add(DfsReftable table) {
+ srcReftables.add(table);
+ return this;
+ }
+
+ /**
+ * Automatically select pack and reftables to be included, and add them.
* <p>
* Packs are selected based on size, smaller packs get included while bigger
* ones are omitted.
@@ -155,6 +184,16 @@ public DfsPackCompactor autoAdd() throws IOException {
else
exclude(pack);
}
+
+ if (reftableConfig != null) {
+ for (DfsReftable table : objdb.getReftables()) {
+ DfsPackDescription d = table.getPackDescription();
+ if (d.getPackSource() != GC
+ && d.getFileSize(REFTABLE) < autoAddSize) {
+ add(table);
+ }
+ }
+ }
return this;
}
@@ -197,61 +236,71 @@ public DfsPackCompactor exclude(DfsPackFile pack) throws IOException {
* the packs cannot be compacted.
*/
public void compact(ProgressMonitor pm) throws IOException {
- if (pm == null)
+ if (pm == null) {
pm = NullProgressMonitor.INSTANCE;
+ }
DfsObjDatabase objdb = repo.getObjectDatabase();
try (DfsReader ctx = objdb.newReader()) {
- PackConfig pc = new PackConfig(repo);
- pc.setIndexVersion(2);
- pc.setDeltaCompress(false);
- pc.setReuseDeltas(true);
- pc.setReuseObjects(true);
+ if (reftableConfig != null && !srcReftables.isEmpty()) {
+ compactReftables(ctx);
+ }
+ compactPacks(ctx, pm);
- PackWriter pw = new PackWriter(pc, ctx);
- try {
- pw.setDeltaBaseAsOffset(true);
- pw.setReuseDeltaCommits(false);
-
- addObjectsToPack(pw, ctx, pm);
- if (pw.getObjectCount() == 0) {
- List<DfsPackDescription> remove = toPrune();
- if (remove.size() > 0)
- objdb.commitPack(
- Collections.<DfsPackDescription>emptyList(),
- remove);
- return;
- }
-
- boolean rollback = true;
- DfsPackDescription pack = objdb.newPack(COMPACT,
- estimatePackSize());
- try {
- writePack(objdb, pack, pw, pm);
- writeIndex(objdb, pack, pw);
-
- PackStatistics stats = pw.getStatistics();
- pw.close();
- pw = null;
-
- pack.setPackStats(stats);
- objdb.commitPack(Collections.singletonList(pack), toPrune());
- newPacks.add(pack);
- newStats.add(stats);
- rollback = false;
- } finally {
- if (rollback)
- objdb.rollbackPack(Collections.singletonList(pack));
- }
- } finally {
- if (pw != null)
- pw.close();
+ List<DfsPackDescription> commit = getNewPacks();
+ Collection<DfsPackDescription> remove = toPrune();
+ if (!commit.isEmpty() || !remove.isEmpty()) {
+ objdb.commitPack(commit, remove);
}
} finally {
rw = null;
}
}
+ private void compactPacks(DfsReader ctx, ProgressMonitor pm)
+ throws IOException, IncorrectObjectTypeException {
+ DfsObjDatabase objdb = repo.getObjectDatabase();
+ PackConfig pc = new PackConfig(repo);
+ pc.setIndexVersion(2);
+ pc.setDeltaCompress(false);
+ pc.setReuseDeltas(true);
+ pc.setReuseObjects(true);
+
+ PackWriter pw = new PackWriter(pc, ctx);
+ try {
+ pw.setDeltaBaseAsOffset(true);
+ pw.setReuseDeltaCommits(false);
+
+ addObjectsToPack(pw, ctx, pm);
+ if (pw.getObjectCount() == 0) {
+ return;
+ }
+
+ boolean rollback = true;
+ initOutDesc(objdb);
+ try {
+ writePack(objdb, outDesc, pw, pm);
+ writeIndex(objdb, outDesc, pw);
+
+ PackStatistics stats = pw.getStatistics();
+ pw.close();
+ pw = null;
+
+ outDesc.setPackStats(stats);
+ newStats = stats;
+ rollback = false;
+ } finally {
+ if (rollback) {
+ objdb.rollbackPack(Collections.singletonList(outDesc));
+ }
+ }
+ } finally {
+ if (pw != null) {
+ pw.close();
+ }
+ }
+ }
+
private long estimatePackSize() {
// Every pack file contains 12 bytes of header and 20 bytes of trailer.
// Include the final pack file header and trailer size here and ignore
@@ -263,27 +312,81 @@ private long estimatePackSize() {
return size;
}
+ private void compactReftables(DfsReader ctx) throws IOException {
+ DfsObjDatabase objdb = repo.getObjectDatabase();
+ Collections.sort(srcReftables, objdb.reftableComparator());
+
+ try (ReftableStack stack = ReftableStack.open(ctx, srcReftables)) {
+ initOutDesc(objdb);
+ ReftableCompactor compact = new ReftableCompactor();
+ compact.addAll(stack.readers());
+ compact.setIncludeDeletes(true);
+ writeReftable(objdb, outDesc, compact);
+ }
+ }
+
+ private void initOutDesc(DfsObjDatabase objdb) throws IOException {
+ if (outDesc == null) {
+ outDesc = objdb.newPack(COMPACT, estimatePackSize());
+ }
+ }
+
/** @return all of the source packs that fed into this compaction. */
- public List<DfsPackDescription> getSourcePacks() {
- return toPrune();
+ public Collection<DfsPackDescription> getSourcePacks() {
+ Set<DfsPackDescription> src = new HashSet<>();
+ for (DfsPackFile pack : srcPacks) {
+ src.add(pack.getPackDescription());
+ }
+ for (DfsReftable table : srcReftables) {
+ src.add(table.getPackDescription());
+ }
+ return src;
}
/** @return new packs created by this compaction. */
public List<DfsPackDescription> getNewPacks() {
- return newPacks;
+ return outDesc != null
+ ? Collections.singletonList(outDesc)
+ : Collections.emptyList();
}
/** @return statistics corresponding to the {@link #getNewPacks()}. */
public List<PackStatistics> getNewPackStatistics() {
- return newStats;
+ return newStats != null
+ ? Collections.singletonList(newStats)
+ : Collections.emptyList();
}
- private List<DfsPackDescription> toPrune() {
- int cnt = srcPacks.size();
- List<DfsPackDescription> all = new ArrayList<>(cnt);
- for (DfsPackFile pack : srcPacks)
- all.add(pack.getPackDescription());
- return all;
+ private Collection<DfsPackDescription> toPrune() {
+ Set<DfsPackDescription> packs = new HashSet<>();
+ for (DfsPackFile pack : srcPacks) {
+ packs.add(pack.getPackDescription());
+ }
+
+ Set<DfsPackDescription> reftables = new HashSet<>();
+ for (DfsReftable table : srcReftables) {
+ reftables.add(table.getPackDescription());
+ }
+
+ for (Iterator<DfsPackDescription> i = packs.iterator(); i.hasNext();) {
+ DfsPackDescription d = i.next();
+ if (d.hasFileExt(REFTABLE) && !reftables.contains(d)) {
+ i.remove();
+ }
+ }
+
+ for (Iterator<DfsPackDescription> i = reftables.iterator();
+ i.hasNext();) {
+ DfsPackDescription d = i.next();
+ if (d.hasFileExt(PACK) && !packs.contains(d)) {
+ i.remove();
+ }
+ }
+
+ Set<DfsPackDescription> toPrune = new HashSet<>();
+ toPrune.addAll(packs);
+ toPrune.addAll(reftables);
+ return toPrune;
}
private void addObjectsToPack(PackWriter pw, DfsReader ctx,
@@ -370,30 +473,47 @@ public int compare(ObjectIdWithOffset a, ObjectIdWithOffset b) {
private static void writePack(DfsObjDatabase objdb,
DfsPackDescription pack,
PackWriter pw, ProgressMonitor pm) throws IOException {
- DfsOutputStream out = objdb.writeFile(pack, PACK);
- try {
+ try (DfsOutputStream out = objdb.writeFile(pack, PACK)) {
pw.writePack(pm, pm, out);
pack.addFileExt(PACK);
- } finally {
- out.close();
+ pack.setBlockSize(PACK, out.blockSize());
}
}
private static void writeIndex(DfsObjDatabase objdb,
DfsPackDescription pack,
PackWriter pw) throws IOException {
- DfsOutputStream out = objdb.writeFile(pack, INDEX);
- try {
+ try (DfsOutputStream out = objdb.writeFile(pack, INDEX)) {
CountingOutputStream cnt = new CountingOutputStream(out);
pw.writeIndex(cnt);
pack.addFileExt(INDEX);
pack.setFileSize(INDEX, cnt.getCount());
+ pack.setBlockSize(INDEX, out.blockSize());
pack.setIndexVersion(pw.getIndexVersion());
- } finally {
- out.close();
}
}
+ private void writeReftable(DfsObjDatabase objdb, DfsPackDescription pack,
+ ReftableCompactor compact) throws IOException {
+ try (DfsOutputStream out = objdb.writeFile(pack, REFTABLE)) {
+ compact.setConfig(configureReftable(reftableConfig, out));
+ compact.compact(out);
+ pack.addFileExt(REFTABLE);
+ pack.setReftableStats(compact.getStats());
+ }
+ }
+
+ static ReftableConfig configureReftable(ReftableConfig cfg,
+ DfsOutputStream out) {
+ int bs = out.blockSize();
+ if (bs > 0) {
+ cfg = new ReftableConfig(cfg);
+ cfg.setRefBlockSize(bs);
+ cfg.setAlignBlocks(true);
+ }
+ return cfg;
+ }
+
private static class ObjectIdWithOffset extends ObjectId {
final long offset;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java
index e825f1a..e865e6b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java
@@ -44,12 +44,13 @@
package org.eclipse.jgit.internal.storage.dfs;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.Arrays;
import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
import org.eclipse.jgit.storage.pack.PackStatistics;
/**
@@ -62,25 +63,20 @@
*/
public class DfsPackDescription implements Comparable<DfsPackDescription> {
private final DfsRepositoryDescription repoDesc;
-
private final String packName;
-
private PackSource packSource;
-
private long lastModified;
-
- private final Map<PackExt, Long> sizeMap;
-
+ private long[] sizeMap;
+ private int[] blockSizeMap;
private long objectCount;
-
private long deltaCount;
+ private long minUpdateIndex;
+ private long maxUpdateIndex;
- private PackStatistics stats;
-
+ private PackStatistics packStats;
+ private ReftableWriter.Stats refStats;
private int extensions;
-
private int indexVersion;
-
private long estimatedPackSize;
/**
@@ -102,7 +98,10 @@ public DfsPackDescription(DfsRepositoryDescription repoDesc, String name) {
this.repoDesc = repoDesc;
int dot = name.lastIndexOf('.');
this.packName = (dot < 0) ? name : name.substring(0, dot);
- this.sizeMap = new HashMap<>(PackExt.values().length * 2);
+
+ int extCnt = PackExt.values().length;
+ sizeMap = new long[extCnt];
+ blockSizeMap = new int[extCnt];
}
/** @return description of the repository. */
@@ -138,6 +137,15 @@ public String getFileName(PackExt ext) {
return packName + '.' + ext.getExtension();
}
+ /**
+ * @param ext
+ * the file extension.
+ * @return cache key for use by the block cache.
+ */
+ public DfsStreamKey getStreamKey(PackExt ext) {
+ return DfsStreamKey.of(getRepositoryDescription(), getFileName(ext));
+ }
+
/** @return the source of the pack. */
public PackSource getPackSource() {
return packSource;
@@ -168,6 +176,36 @@ public DfsPackDescription setLastModified(long timeMillis) {
return this;
}
+ /** @return minUpdateIndex for the reftable, if present. */
+ public long getMinUpdateIndex() {
+ return minUpdateIndex;
+ }
+
+ /**
+ * @param min
+ * minUpdateIndex for the reftable, or 0.
+ * @return {@code this}
+ */
+ public DfsPackDescription setMinUpdateIndex(long min) {
+ minUpdateIndex = min;
+ return this;
+ }
+
+ /** @return maxUpdateIndex for the reftable, if present. */
+ public long getMaxUpdateIndex() {
+ return maxUpdateIndex;
+ }
+
+ /**
+ * @param max
+ * maxUpdateIndex for the reftable, or 0.
+ * @return {@code this}
+ */
+ public DfsPackDescription setMaxUpdateIndex(long max) {
+ maxUpdateIndex = max;
+ return this;
+ }
+
/**
* @param ext
* the file extension.
@@ -177,7 +215,11 @@ public DfsPackDescription setLastModified(long timeMillis) {
* @return {@code this}
*/
public DfsPackDescription setFileSize(PackExt ext, long bytes) {
- sizeMap.put(ext, Long.valueOf(Math.max(0, bytes)));
+ int i = ext.getPosition();
+ if (i >= sizeMap.length) {
+ sizeMap = Arrays.copyOf(sizeMap, i + 1);
+ }
+ sizeMap[i] = Math.max(0, bytes);
return this;
}
@@ -187,8 +229,36 @@ public DfsPackDescription setFileSize(PackExt ext, long bytes) {
* @return size of the file, in bytes. If 0 the file size is not yet known.
*/
public long getFileSize(PackExt ext) {
- Long size = sizeMap.get(ext);
- return size == null ? 0 : size.longValue();
+ int i = ext.getPosition();
+ return i < sizeMap.length ? sizeMap[i] : 0;
+ }
+
+ /**
+ * @param ext
+ * the file extension.
+ * @return blockSize of the file, in bytes. If 0 the blockSize size is not
+ * yet known and may be discovered when opening the file.
+ */
+ public int getBlockSize(PackExt ext) {
+ int i = ext.getPosition();
+ return i < blockSizeMap.length ? blockSizeMap[i] : 0;
+ }
+
+ /**
+ * @param ext
+ * the file extension.
+ * @param blockSize
+ * blockSize of the file, in bytes. If 0 the blockSize is not
+ * known and will be determined on first read.
+ * @return {@code this}
+ */
+ public DfsPackDescription setBlockSize(PackExt ext, int blockSize) {
+ int i = ext.getPosition();
+ if (i >= blockSizeMap.length) {
+ blockSizeMap = Arrays.copyOf(blockSizeMap, i + 1);
+ }
+ blockSizeMap[i] = Math.max(0, blockSize);
+ return this;
}
/**
@@ -247,24 +317,38 @@ public DfsPackDescription setDeltaCount(long cnt) {
* is being committed to the repository.
*/
public PackStatistics getPackStats() {
- return stats;
+ return packStats;
}
DfsPackDescription setPackStats(PackStatistics stats) {
- this.stats = stats;
+ this.packStats = stats;
setFileSize(PACK, stats.getTotalBytes());
setObjectCount(stats.getTotalObjects());
setDeltaCount(stats.getTotalDeltas());
return this;
}
+ /** @return stats from the sibling reftable, if created. */
+ public ReftableWriter.Stats getReftableStats() {
+ return refStats;
+ }
+
+ void setReftableStats(ReftableWriter.Stats stats) {
+ this.refStats = stats;
+ setMinUpdateIndex(stats.minUpdateIndex());
+ setMaxUpdateIndex(stats.maxUpdateIndex());
+ setFileSize(REFTABLE, stats.totalBytes());
+ setBlockSize(REFTABLE, stats.refBlockSize());
+ }
+
/**
* Discard the pack statistics, if it was populated.
*
* @return {@code this}
*/
public DfsPackDescription clearPackStats() {
- stats = null;
+ packStats = null;
+ refStats = null;
return this;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
index ae2e7e4..dfb41e2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
@@ -72,7 +72,6 @@
import org.eclipse.jgit.internal.storage.file.PackIndex;
import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
import org.eclipse.jgit.internal.storage.pack.BinaryDelta;
-import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
import org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
@@ -88,53 +87,7 @@
* delta packed format yielding high compression of lots of object where some
* objects are similar.
*/
-public final class DfsPackFile {
- /**
- * File offset used to cache {@link #index} in {@link DfsBlockCache}.
- * <p>
- * To better manage memory, the forward index is stored as a single block in
- * the block cache under this file position. A negative value is used
- * because it cannot occur in a normal pack file, and it is less likely to
- * collide with a valid data block from the file as the high bits will all
- * be set when treated as an unsigned long by the cache code.
- */
- private static final long POS_INDEX = -1;
-
- /** Offset used to cache {@link #reverseIndex}. See {@link #POS_INDEX}. */
- private static final long POS_REVERSE_INDEX = -2;
-
- /** Offset used to cache {@link #bitmapIndex}. See {@link #POS_INDEX}. */
- private static final long POS_BITMAP_INDEX = -3;
-
- /** Cache that owns this pack file and its data. */
- private final DfsBlockCache cache;
-
- /** Description of the pack file's storage. */
- private final DfsPackDescription packDesc;
-
- /** Unique identity of this pack while in-memory. */
- final DfsPackKey key;
-
- /**
- * Total number of bytes in this pack file.
- * <p>
- * This field initializes to -1 and gets populated when a block is loaded.
- */
- volatile long length;
-
- /**
- * Preferred alignment for loading blocks from the backing file.
- * <p>
- * It is initialized to 0 and filled in on the first read made from the
- * file. Block sizes may be odd, e.g. 4091, caused by the underling DFS
- * storing 4091 user bytes and 5 bytes block metadata into a lower level
- * 4096 byte block on disk.
- */
- private volatile int blockSize;
-
- /** True once corruption has been detected that cannot be worked around. */
- private volatile boolean invalid;
-
+public final class DfsPackFile extends BlockBasedFile {
/**
* Lock for initialization of {@link #index} and {@link #corruptObjects}.
* <p>
@@ -167,22 +120,22 @@ public final class DfsPackFile {
* cache that owns the pack data.
* @param desc
* description of the pack within the DFS.
- * @param key
- * interned key used to identify blocks in the block cache.
*/
- DfsPackFile(DfsBlockCache cache, DfsPackDescription desc, DfsPackKey key) {
- this.cache = cache;
- this.packDesc = desc;
- this.key = key;
+ DfsPackFile(DfsBlockCache cache, DfsPackDescription desc) {
+ super(cache, desc, PACK);
- length = desc.getFileSize(PACK);
- if (length <= 0)
- length = -1;
+ int bs = desc.getBlockSize(PACK);
+ if (bs > 0) {
+ setBlockSize(bs);
+ }
+
+ long sz = desc.getFileSize(PACK);
+ length = sz > 0 ? sz : -1;
}
/** @return description that was originally used to configure this pack file. */
public DfsPackDescription getPackDescription() {
- return packDesc;
+ return desc;
}
/**
@@ -193,24 +146,11 @@ public boolean isIndexLoaded() {
return idxref != null && idxref.has();
}
- /** @return bytes cached in memory for this pack, excluding the index. */
- public long getCachedSize() {
- return key.cachedSize.get();
- }
-
- String getPackName() {
- return packDesc.getFileName(PACK);
- }
-
- void setBlockSize(int newSize) {
- blockSize = newSize;
- }
-
void setPackIndex(PackIndex idx) {
long objCnt = idx.getObjectCount();
int recSize = Constants.OBJECT_ID_LENGTH + 8;
- int sz = (int) Math.min(objCnt * recSize, Integer.MAX_VALUE);
- index = cache.put(key, POS_INDEX, sz, idx);
+ long sz = objCnt * recSize;
+ index = cache.putRef(desc.getStreamKey(INDEX), sz, idx);
}
/**
@@ -236,7 +176,7 @@ private PackIndex idx(DfsReader ctx) throws IOException {
}
if (invalid)
- throw new PackInvalidException(getPackName());
+ throw new PackInvalidException(getFileName());
Repository.getGlobalListenerList()
.dispatch(new BeforeDfsPackIndexLoadedEvent(this));
@@ -249,11 +189,21 @@ private PackIndex idx(DfsReader ctx) throws IOException {
return idx;
}
+ DfsStreamKey idxKey = desc.getStreamKey(INDEX);
+ idxref = cache.getRef(idxKey);
+ if (idxref != null) {
+ PackIndex idx = idxref.get();
+ if (idx != null) {
+ index = idxref;
+ return idx;
+ }
+ }
+
PackIndex idx;
try {
ctx.stats.readIdx++;
long start = System.nanoTime();
- ReadableChannel rc = ctx.db.openFile(packDesc, INDEX);
+ ReadableChannel rc = ctx.db.openFile(desc, INDEX);
try {
InputStream in = Channels.newInputStream(rc);
int wantSize = 8192;
@@ -270,18 +220,14 @@ else if (bs <= 0)
}
} catch (EOFException e) {
invalid = true;
- IOException e2 = new IOException(MessageFormat.format(
+ throw new IOException(MessageFormat.format(
DfsText.get().shortReadOfIndex,
- packDesc.getFileName(INDEX)));
- e2.initCause(e);
- throw e2;
+ desc.getFileName(INDEX)), e);
} catch (IOException e) {
invalid = true;
- IOException e2 = new IOException(MessageFormat.format(
+ throw new IOException(MessageFormat.format(
DfsText.get().cannotReadIndex,
- packDesc.getFileName(INDEX)));
- e2.initCause(e);
- throw e2;
+ desc.getFileName(INDEX)), e);
}
setPackIndex(idx);
@@ -289,17 +235,14 @@ else if (bs <= 0)
}
}
- private static long elapsedMicros(long start) {
- return (System.nanoTime() - start) / 1000L;
- }
-
final boolean isGarbage() {
- return packDesc.getPackSource() == UNREACHABLE_GARBAGE;
+ return desc.getPackSource() == UNREACHABLE_GARBAGE;
}
PackBitmapIndex getBitmapIndex(DfsReader ctx) throws IOException {
- if (invalid || isGarbage())
+ if (invalid || isGarbage() || !desc.hasFileExt(BITMAP_INDEX))
return null;
+
DfsBlockCache.Ref<PackBitmapIndex> idxref = bitmapIndex;
if (idxref != null) {
PackBitmapIndex idx = idxref.get();
@@ -307,9 +250,6 @@ PackBitmapIndex getBitmapIndex(DfsReader ctx) throws IOException {
return idx;
}
- if (!packDesc.hasFileExt(PackExt.BITMAP_INDEX))
- return null;
-
synchronized (initLock) {
idxref = bitmapIndex;
if (idxref != null) {
@@ -318,12 +258,22 @@ PackBitmapIndex getBitmapIndex(DfsReader ctx) throws IOException {
return idx;
}
+ DfsStreamKey bitmapKey = desc.getStreamKey(BITMAP_INDEX);
+ idxref = cache.getRef(bitmapKey);
+ if (idxref != null) {
+ PackBitmapIndex idx = idxref.get();
+ if (idx != null) {
+ bitmapIndex = idxref;
+ return idx;
+ }
+ }
+
long size;
PackBitmapIndex idx;
try {
ctx.stats.readBitmap++;
long start = System.nanoTime();
- ReadableChannel rc = ctx.db.openFile(packDesc, BITMAP_INDEX);
+ ReadableChannel rc = ctx.db.openFile(desc, BITMAP_INDEX);
try {
InputStream in = Channels.newInputStream(rc);
int wantSize = 8192;
@@ -342,21 +292,16 @@ else if (bs <= 0)
ctx.stats.readIdxMicros += elapsedMicros(start);
}
} catch (EOFException e) {
- IOException e2 = new IOException(MessageFormat.format(
+ throw new IOException(MessageFormat.format(
DfsText.get().shortReadOfIndex,
- packDesc.getFileName(BITMAP_INDEX)));
- e2.initCause(e);
- throw e2;
+ desc.getFileName(BITMAP_INDEX)), e);
} catch (IOException e) {
- IOException e2 = new IOException(MessageFormat.format(
+ throw new IOException(MessageFormat.format(
DfsText.get().cannotReadIndex,
- packDesc.getFileName(BITMAP_INDEX)));
- e2.initCause(e);
- throw e2;
+ desc.getFileName(BITMAP_INDEX)), e);
}
- bitmapIndex = cache.put(key, POS_BITMAP_INDEX,
- (int) Math.min(size, Integer.MAX_VALUE), idx);
+ bitmapIndex = cache.putRef(bitmapKey, size, idx);
return idx;
}
}
@@ -377,11 +322,21 @@ PackReverseIndex getReverseIdx(DfsReader ctx) throws IOException {
return revidx;
}
+ DfsStreamKey revKey =
+ new DfsStreamKey.ForReverseIndex(desc.getStreamKey(INDEX));
+ revref = cache.getRef(revKey);
+ if (revref != null) {
+ PackReverseIndex idx = revref.get();
+ if (idx != null) {
+ reverseIndex = revref;
+ return idx;
+ }
+ }
+
PackIndex idx = idx(ctx);
PackReverseIndex revidx = new PackReverseIndex(idx);
- int sz = (int) Math.min(
- idx.getObjectCount() * 8, Integer.MAX_VALUE);
- reverseIndex = cache.put(key, POS_REVERSE_INDEX, sz, revidx);
+ long cnt = idx.getObjectCount();
+ reverseIndex = cache.putRef(revKey, cnt * 8, revidx);
return revidx;
}
}
@@ -430,13 +385,6 @@ void resolve(DfsReader ctx, Set<ObjectId> matches, AbbreviatedObjectId id,
idx(ctx).resolve(matches, id, matchLimit);
}
- /** Release all memory used by this DfsPackFile instance. */
- public void close() {
- cache.remove(this);
- index = null;
- reverseIndex = null;
- }
-
/**
* Obtain the total number of objects available in this pack. This method
* relies on pack index, giving number of effectively available objects.
@@ -489,21 +437,42 @@ void copyPackAsIs(PackOutputStream out, DfsReader ctx)
private void copyPackThroughCache(PackOutputStream out, DfsReader ctx)
throws IOException {
- long position = 12;
- long remaining = length - (12 + 20);
- while (0 < remaining) {
- DfsBlock b = cache.getOrLoad(this, position, ctx);
- int ptr = (int) (position - b.start);
- int n = (int) Math.min(b.size() - ptr, remaining);
- b.write(out, position, n);
- position += n;
- remaining -= n;
+ ReadableChannel rc = null;
+ try {
+ long position = 12;
+ long remaining = length - (12 + 20);
+ while (0 < remaining) {
+ DfsBlock b;
+ if (rc != null) {
+ b = cache.getOrLoad(this, position, ctx, rc);
+ } else {
+ b = cache.get(key, alignToBlock(position));
+ if (b == null) {
+ rc = ctx.db.openFile(desc, PACK);
+ int sz = ctx.getOptions().getStreamPackBufferSize();
+ if (sz > 0) {
+ rc.setReadAheadBytes(sz);
+ }
+ b = cache.getOrLoad(this, position, ctx, rc);
+ }
+ }
+
+ int ptr = (int) (position - b.start);
+ int n = (int) Math.min(b.size() - ptr, remaining);
+ b.write(out, position, n);
+ position += n;
+ remaining -= n;
+ }
+ } finally {
+ if (rc != null) {
+ rc.close();
+ }
}
}
private long copyPackBypassCache(PackOutputStream out, DfsReader ctx)
throws IOException {
- try (ReadableChannel rc = ctx.db.openFile(packDesc, PACK)) {
+ try (ReadableChannel rc = ctx.db.openFile(desc, PACK)) {
ByteBuffer buf = newCopyBuffer(out, rc);
if (ctx.getOptions().getStreamPackBufferSize() > 0)
rc.setReadAheadBytes(ctx.getOptions().getStreamPackBufferSize());
@@ -642,7 +611,7 @@ void copyAsIs(PackOutputStream out, DfsObjectToPack src,
setCorrupt(src.offset);
throw new CorruptObjectException(MessageFormat.format(
JGitText.get().objectAtHasBadZlibStream,
- Long.valueOf(src.offset), getPackName()));
+ Long.valueOf(src.offset), getFileName()));
}
} else if (validate) {
assert(crc1 != null);
@@ -684,7 +653,7 @@ void copyAsIs(PackOutputStream out, DfsObjectToPack src,
CorruptObjectException corruptObject = new CorruptObjectException(
MessageFormat.format(
JGitText.get().objectAtHasBadZlibStream,
- Long.valueOf(src.offset), getPackName()));
+ Long.valueOf(src.offset), getFileName()));
corruptObject.initCause(dataFormat);
StoredObjectRepresentationNotAvailableException gone;
@@ -746,24 +715,16 @@ void copyAsIs(PackOutputStream out, DfsObjectToPack src,
if (crc2.getValue() != expectedCRC) {
throw new CorruptObjectException(MessageFormat.format(
JGitText.get().objectAtHasBadZlibStream,
- Long.valueOf(src.offset), getPackName()));
+ Long.valueOf(src.offset), getFileName()));
}
}
}
}
- boolean invalid() {
- return invalid;
- }
-
- void setInvalid() {
- invalid = true;
- }
-
private IOException packfileIsTruncated() {
invalid = true;
return new IOException(MessageFormat.format(
- JGitText.get().packfileIsTruncated, getPackName()));
+ JGitText.get().packfileIsTruncated, getFileName()));
}
private void readFully(long position, byte[] dstbuf, int dstoff, int cnt,
@@ -772,103 +733,6 @@ private void readFully(long position, byte[] dstbuf, int dstoff, int cnt,
throw new EOFException();
}
- long alignToBlock(long pos) {
- int size = blockSize;
- if (size == 0)
- size = cache.getBlockSize();
- return (pos / size) * size;
- }
-
- DfsBlock getOrLoadBlock(long pos, DfsReader ctx) throws IOException {
- return cache.getOrLoad(this, pos, ctx);
- }
-
- DfsBlock readOneBlock(long pos, DfsReader ctx)
- throws IOException {
- if (invalid)
- throw new PackInvalidException(getPackName());
-
- ctx.stats.readBlock++;
- long start = System.nanoTime();
- ReadableChannel rc = ctx.db.openFile(packDesc, PACK);
- try {
- int size = blockSize(rc);
- pos = (pos / size) * size;
-
- // If the size of the file is not yet known, try to discover it.
- // Channels may choose to return -1 to indicate they don't
- // know the length yet, in this case read up to the size unit
- // given by the caller, then recheck the length.
- long len = length;
- if (len < 0) {
- len = rc.size();
- if (0 <= len)
- length = len;
- }
-
- if (0 <= len && len < pos + size)
- size = (int) (len - pos);
- if (size <= 0)
- throw new EOFException(MessageFormat.format(
- DfsText.get().shortReadOfBlock, Long.valueOf(pos),
- getPackName(), Long.valueOf(0), Long.valueOf(0)));
-
- byte[] buf = new byte[size];
- rc.position(pos);
- int cnt = read(rc, ByteBuffer.wrap(buf, 0, size));
- ctx.stats.readBlockBytes += cnt;
- if (cnt != size) {
- if (0 <= len) {
- throw new EOFException(MessageFormat.format(
- DfsText.get().shortReadOfBlock,
- Long.valueOf(pos),
- getPackName(),
- Integer.valueOf(size),
- Integer.valueOf(cnt)));
- }
-
- // Assume the entire thing was read in a single shot, compact
- // the buffer to only the space required.
- byte[] n = new byte[cnt];
- System.arraycopy(buf, 0, n, 0, n.length);
- buf = n;
- } else if (len < 0) {
- // With no length at the start of the read, the channel should
- // have the length available at the end.
- length = len = rc.size();
- }
-
- return new DfsBlock(key, pos, buf);
- } finally {
- rc.close();
- ctx.stats.readBlockMicros += elapsedMicros(start);
- }
- }
-
- private int blockSize(ReadableChannel rc) {
- // If the block alignment is not yet known, discover it. Prefer the
- // larger size from either the cache or the file itself.
- int size = blockSize;
- if (size == 0) {
- size = rc.blockSize();
- if (size <= 0)
- size = cache.getBlockSize();
- else if (size < cache.getBlockSize())
- size = (cache.getBlockSize() / size) * size;
- blockSize = size;
- }
- return size;
- }
-
- private static int read(ReadableChannel rc, ByteBuffer buf)
- throws IOException {
- int n;
- do {
- n = rc.read(buf);
- } while (0 < n && buf.hasRemaining());
- return buf.position();
- }
-
ObjectLoader load(DfsReader ctx, long pos)
throws IOException {
try {
@@ -1005,7 +869,7 @@ else if (delta.next == null)
CorruptObjectException coe = new CorruptObjectException(
MessageFormat.format(
JGitText.get().objectAtHasBadZlibStream, Long.valueOf(pos),
- getPackName()));
+ getFileName()));
coe.initCause(dfe);
throw coe;
}
@@ -1153,7 +1017,7 @@ long getObjectSize(DfsReader ctx, long pos)
CorruptObjectException coe = new CorruptObjectException(
MessageFormat.format(
JGitText.get().objectAtHasBadZlibStream, Long.valueOf(pos),
- getPackName()));
+ getFileName()));
coe.initCause(dfe);
throw coe;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java
index 6430ea9..fd99db1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java
@@ -94,7 +94,7 @@ public class DfsPackParser extends PackParser {
private DfsPackDescription packDsc;
/** Key used during delta resolution reading delta chains. */
- private DfsPackKey packKey;
+ private DfsStreamKey packKey;
/** If the index was small enough, the entire index after writing. */
private PackIndex packIndex;
@@ -150,12 +150,13 @@ public PackLock parse(ProgressMonitor receiving, ProgressMonitor resolving)
readBlock = null;
packDsc.addFileExt(PACK);
packDsc.setFileSize(PACK, packEnd);
+ packDsc.setBlockSize(PACK, blockSize);
writePackIndex();
objdb.commitPack(Collections.singletonList(packDsc), null);
rollback = false;
- DfsPackFile p = blockCache.getOrCreate(packDsc, packKey);
+ DfsPackFile p = new DfsPackFile(blockCache, packDsc);
p.setBlockSize(blockSize);
if (packIndex != null)
p.setPackIndex(packIndex);
@@ -206,9 +207,9 @@ protected void onPackHeader(long objectCount) throws IOException {
}
packDsc = objdb.newPack(DfsObjDatabase.PackSource.RECEIVE);
- packKey = new DfsPackKey();
-
out = objdb.writeFile(packDsc, PACK);
+ packKey = packDsc.getStreamKey(PACK);
+
int size = out.blockSize();
if (size <= 0)
size = blockCache.getBlockSize();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
index d611469..3c84220 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
@@ -655,7 +655,7 @@ public void copyPackAsIs(PackOutputStream out, CachedPack pack)
/**
* Copy bytes from the window to a caller supplied buffer.
*
- * @param pack
+ * @param file
* the file the desired window is stored within.
* @param position
* position within the file to read from.
@@ -674,24 +674,24 @@ public void copyPackAsIs(PackOutputStream out, CachedPack pack)
* this cursor does not match the provider or id and the proper
* window could not be acquired through the provider's cache.
*/
- int copy(DfsPackFile pack, long position, byte[] dstbuf, int dstoff, int cnt)
- throws IOException {
+ int copy(BlockBasedFile file, long position, byte[] dstbuf, int dstoff,
+ int cnt) throws IOException {
if (cnt == 0)
return 0;
- long length = pack.length;
+ long length = file.length;
if (0 <= length && length <= position)
return 0;
int need = cnt;
do {
- pin(pack, position);
+ pin(file, position);
int r = block.copy(position, dstbuf, dstoff, need);
position += r;
dstoff += r;
need -= r;
if (length < 0)
- length = pack.length;
+ length = file.length;
} while (0 < need && position < length);
return cnt - need;
}
@@ -756,15 +756,14 @@ private void prepareInflater() {
inf.reset();
}
- void pin(DfsPackFile pack, long position) throws IOException {
- DfsBlock b = block;
- if (b == null || !b.contains(pack.key, position)) {
+ void pin(BlockBasedFile file, long position) throws IOException {
+ if (block == null || !block.contains(file.key, position)) {
// If memory is low, we may need what is in our window field to
// be cleaned up by the GC during the get for the next window.
// So we always clear it, even though we are just going to set
// it again.
block = null;
- block = pack.getOrLoadBlock(position, this);
+ block = file.getOrLoadBlock(position, this);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftable.java
new file mode 100644
index 0000000..5a8ea92
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftable.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jgit.internal.storage.io.BlockSource;
+import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
+
+/** A reftable stored in {@link DfsBlockCache}. */
+public class DfsReftable extends BlockBasedFile {
+ /**
+ * Construct a reader for an existing reftable.
+ *
+ * @param desc
+ * description of the reftable within the DFS.
+ */
+ public DfsReftable(DfsPackDescription desc) {
+ this(DfsBlockCache.getInstance(), desc);
+ }
+
+ /**
+ * Construct a reader for an existing reftable.
+ *
+ * @param cache
+ * cache that will store the reftable data.
+ * @param desc
+ * description of the reftable within the DFS.
+ */
+ public DfsReftable(DfsBlockCache cache, DfsPackDescription desc) {
+ super(cache, desc, REFTABLE);
+
+ int bs = desc.getBlockSize(REFTABLE);
+ if (bs > 0) {
+ setBlockSize(bs);
+ }
+
+ long sz = desc.getFileSize(REFTABLE);
+ length = sz > 0 ? sz : -1;
+ }
+
+ /** @return description that was originally used to configure this file. */
+ public DfsPackDescription getPackDescription() {
+ return desc;
+ }
+
+ /**
+ * Open reader on the reftable.
+ * <p>
+ * The returned reader is not thread safe.
+ *
+ * @param ctx
+ * reader to access the DFS storage.
+ * @return cursor to read the table; caller must close.
+ * @throws IOException
+ * table cannot be opened.
+ */
+ public ReftableReader open(DfsReader ctx) throws IOException {
+ return new ReftableReader(new CacheSource(this, cache, ctx));
+ }
+
+ private static final class CacheSource extends BlockSource {
+ private final DfsReftable file;
+ private final DfsBlockCache cache;
+ private final DfsReader ctx;
+ private ReadableChannel ch;
+ private int readAhead;
+
+ CacheSource(DfsReftable file, DfsBlockCache cache, DfsReader ctx) {
+ this.file = file;
+ this.cache = cache;
+ this.ctx = ctx;
+ }
+
+ @Override
+ public ByteBuffer read(long pos, int cnt) throws IOException {
+ if (ch == null && readAhead > 0 && notInCache(pos)) {
+ open().setReadAheadBytes(readAhead);
+ }
+
+ DfsBlock block = cache.getOrLoad(file, pos, ctx, ch);
+ if (block.start == pos && block.size() >= cnt) {
+ return block.zeroCopyByteBuffer(cnt);
+ }
+
+ byte[] dst = new byte[cnt];
+ ByteBuffer buf = ByteBuffer.wrap(dst);
+ buf.position(ctx.copy(file, pos, dst, 0, cnt));
+ return buf;
+ }
+
+ private boolean notInCache(long pos) {
+ return cache.get(file.key, file.alignToBlock(pos)) == null;
+ }
+
+ @Override
+ public long size() throws IOException {
+ long n = file.length;
+ if (n < 0) {
+ n = open().size();
+ file.length = n;
+ }
+ return n;
+ }
+
+ @Override
+ public void adviseSequentialRead(long start, long end) {
+ int sz = ctx.getOptions().getStreamPackBufferSize();
+ if (sz > 0) {
+ readAhead = (int) Math.min(sz, end - start);
+ }
+ }
+
+ private ReadableChannel open() throws IOException {
+ if (ch == null) {
+ ch = ctx.db.openFile(file.desc, file.ext);
+ }
+ return ch;
+ }
+
+ @Override
+ public void close() {
+ if (ch != null) {
+ try {
+ ch.close();
+ } catch (IOException e) {
+ // Ignore read close failures.
+ } finally {
+ ch = null;
+ }
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java
new file mode 100644
index 0000000..54a7489
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2011, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.util.Arrays;
+
+/** Key used by {@link DfsBlockCache} to disambiguate streams. */
+public abstract class DfsStreamKey {
+ /**
+ * @param repo
+ * description of the containing repository.
+ * @param name
+ * compute the key from a string name.
+ * @return key for {@code name}
+ */
+ public static DfsStreamKey of(DfsRepositoryDescription repo, String name) {
+ return new ByteArrayDfsStreamKey(repo, name.getBytes(UTF_8));
+ }
+
+ final int hash;
+
+ /**
+ * @param hash
+ * hash of the other identifying components of the key.
+ */
+ protected DfsStreamKey(int hash) {
+ // Multiply by 31 here so we can more directly combine with another
+ // value without doing the multiply there.
+ this.hash = hash * 31;
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+ @Override
+ public abstract boolean equals(Object o);
+
+ @SuppressWarnings("boxing")
+ @Override
+ public String toString() {
+ return String.format("DfsStreamKey[hash=%08x]", hash); //$NON-NLS-1$
+ }
+
+ private static final class ByteArrayDfsStreamKey extends DfsStreamKey {
+ private final DfsRepositoryDescription repo;
+ private final byte[] name;
+
+ ByteArrayDfsStreamKey(DfsRepositoryDescription repo, byte[] name) {
+ super(repo.hashCode() * 31 + Arrays.hashCode(name));
+ this.repo = repo;
+ this.name = name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof ByteArrayDfsStreamKey) {
+ ByteArrayDfsStreamKey k = (ByteArrayDfsStreamKey) o;
+ return hash == k.hash
+ && repo.equals(k.repo)
+ && Arrays.equals(name, k.name);
+ }
+ return false;
+ }
+ }
+
+ static final class ForReverseIndex extends DfsStreamKey {
+ private final DfsStreamKey idxKey;
+
+ ForReverseIndex(DfsStreamKey idxKey) {
+ super(idxKey.hash + 1);
+ this.idxKey = idxKey;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof ForReverseIndex
+ && idxKey.equals(((ForReverseIndex) o).idxKey);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsText.java
index 8624547..dedcab0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsText.java
@@ -55,9 +55,7 @@ public static DfsText get() {
// @formatter:off
/***/ public String cannotReadIndex;
- /***/ public String cannotReadBackDelta;
/***/ public String shortReadOfBlock;
/***/ public String shortReadOfIndex;
- /***/ public String unexpectedEofInPack;
/***/ public String willNotStoreEmptyPack;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
index 527e46b..1e31878 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
@@ -53,7 +53,7 @@ public InMemoryRepository build() throws IOException {
static final AtomicInteger packId = new AtomicInteger();
- private final DfsObjDatabase objdb;
+ private final MemObjDatabase objdb;
private final RefDatabase refdb;
private String gitwebDescription;
private boolean performsAtomicTransactions = true;
@@ -75,7 +75,7 @@ public InMemoryRepository(DfsRepositoryDescription repoDesc) {
}
@Override
- public DfsObjDatabase getObjectDatabase() {
+ public MemObjDatabase getObjectDatabase() {
return objdb;
}
@@ -106,13 +106,23 @@ public void setGitwebDescription(@Nullable String d) {
gitwebDescription = d;
}
- private class MemObjDatabase extends DfsObjDatabase {
+ /** DfsObjDatabase used by InMemoryRepository. */
+ public static class MemObjDatabase extends DfsObjDatabase {
private List<DfsPackDescription> packs = new ArrayList<>();
+ private int blockSize;
MemObjDatabase(DfsRepository repo) {
super(repo, new DfsReaderOptions());
}
+ /**
+ * @param blockSize
+ * force a different block size for testing.
+ */
+ public void setReadableChannelBlockSizeForTest(int blockSize) {
+ this.blockSize = blockSize;
+ }
+
@Override
protected synchronized List<DfsPackDescription> listPacks() {
return packs;
@@ -152,7 +162,7 @@ protected ReadableChannel openFile(DfsPackDescription desc, PackExt ext)
byte[] file = memPack.fileMap.get(ext);
if (file == null)
throw new FileNotFoundException(desc.getFileName(ext));
- return new ByteArrayReadableChannel(file);
+ return new ByteArrayReadableChannel(file, blockSize);
}
@Override
@@ -216,13 +226,13 @@ public void close() {
private static class ByteArrayReadableChannel implements ReadableChannel {
private final byte[] data;
-
+ private final int blockSize;
private int position;
-
private boolean open = true;
- ByteArrayReadableChannel(byte[] buf) {
+ ByteArrayReadableChannel(byte[] buf, int blockSize) {
data = buf;
+ this.blockSize = blockSize;
}
@Override
@@ -262,7 +272,7 @@ public long size() {
@Override
public int blockSize() {
- return 0;
+ return blockSize;
}
@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableStack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableStack.java
new file mode 100644
index 0000000..8d1cc98
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableStack.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.internal.storage.reftable.Reftable;
+
+/** Tracks multiple open {@link Reftable} instances. */
+public class ReftableStack implements AutoCloseable {
+ /**
+ * Opens a stack of tables for reading.
+ *
+ * @param ctx
+ * context to read the tables with. This {@code ctx} will be
+ * retained by the stack and each of the table readers.
+ * @param tables
+ * the tables to open.
+ * @return stack reference to close the tables.
+ * @throws IOException
+ * a table could not be opened
+ */
+ public static ReftableStack open(DfsReader ctx, List<DfsReftable> tables)
+ throws IOException {
+ ReftableStack stack = new ReftableStack(tables.size());
+ boolean close = true;
+ try {
+ for (DfsReftable t : tables) {
+ stack.tables.add(t.open(ctx));
+ }
+ close = false;
+ return stack;
+ } finally {
+ if (close) {
+ stack.close();
+ }
+ }
+ }
+
+ private final List<Reftable> tables;
+
+ private ReftableStack(int tableCnt) {
+ this.tables = new ArrayList<>(tableCnt);
+ }
+
+ /**
+ * @return unmodifiable list of tables, in the same order the files were
+ * passed to {@link #open(DfsReader, List)}.
+ */
+ public List<Reftable> readers() {
+ return Collections.unmodifiableList(tables);
+ }
+
+ @Override
+ public void close() {
+ for (Reftable t : tables) {
+ try {
+ t.close();
+ } catch (IOException e) {
+ // Ignore close failures.
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java
index 4b4337d..2eacb7a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java
@@ -74,4 +74,4 @@ public String getFromBranch() {
public String getToBranch() {
return to;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
index 6a674aa..646feac 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
@@ -216,7 +216,7 @@ public void onConfigChanged(ConfigChangedEvent event) {
ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
String reftype = repoConfig.getString(
- "extensions", null, "refsStorage"); //$NON-NLS-1$ //$NON-NLS-2$
+ "extensions", null, "refStorage"); //$NON-NLS-1$ //$NON-NLS-2$
if (repositoryFormatVersion >= 1 && reftype != null) {
if (StringUtils.equalsIgnoreCase(reftype, "reftree")) { //$NON-NLS-1$
refs = new RefTreeDatabase(this, new RefDirectory(this));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
index 7ff209f..1b62c14 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
@@ -106,10 +106,10 @@
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Ref.Storage;
-import org.eclipse.jgit.lib.internal.WorkQueue;
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.ReflogReader;
+import org.eclipse.jgit.lib.internal.WorkQueue;
import org.eclipse.jgit.revwalk.ObjectWalk;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -851,6 +851,12 @@ public Collection<PackFile> repack() throws IOException {
tagTargets.addAll(allHeadsAndTags);
nonHeads.addAll(indexObjects);
+ // Combine the GC_REST objects into the GC pack if requested
+ if (pconfig != null && pconfig.getSinglePack()) {
+ allHeadsAndTags.addAll(nonHeads);
+ nonHeads.clear();
+ }
+
List<PackFile> ret = new ArrayList<>(2);
PackFile heads = null;
if (!allHeadsAndTags.isEmpty()) {
@@ -884,6 +890,7 @@ public Collection<PackFile> repack() throws IOException {
prunePacked();
deleteEmptyRefsFolders();
deleteOrphans();
+ deleteTempPacksIdx();
lastPackedRefs = refsBefore;
lastRepackTime = time;
@@ -991,6 +998,28 @@ private void deleteOrphans() {
}
}
+ private void deleteTempPacksIdx() {
+ Path packDir = Paths.get(repo.getObjectsDirectory().getAbsolutePath(),
+ "pack"); //$NON-NLS-1$
+ Instant threshold = Instant.now().minus(1, ChronoUnit.DAYS);
+ try (DirectoryStream<Path> stream =
+ Files.newDirectoryStream(packDir, "gc_*_tmp")) { //$NON-NLS-1$
+ stream.forEach(t -> {
+ try {
+ Instant lastModified = Files.getLastModifiedTime(t)
+ .toInstant();
+ if (lastModified.isBefore(threshold)) {
+ Files.deleteIfExists(t);
+ }
+ } catch (IOException e) {
+ LOG.error(e.getMessage(), e);
+ }
+ });
+ } catch (IOException e) {
+ LOG.error(e.getMessage(), e);
+ }
+ }
+
/**
* @param ref
* the ref which log should be inspected
@@ -1205,16 +1234,7 @@ public int compare(PackExt o1, PackExt o2) {
// rename the temporary files to real files
File realPack = nameFor(id, ".pack"); //$NON-NLS-1$
- // if the packfile already exists (because we are rewriting a
- // packfile for the same set of objects maybe with different
- // PackConfig) then make sure we get rid of all handles on the file.
- // Windows will not allow for rename otherwise.
- if (realPack.exists())
- for (PackFile p : repo.getObjectDatabase().getPacks())
- if (realPack.getPath().equals(p.getPackFile().getPath())) {
- p.close();
- break;
- }
+ repo.getObjectDatabase().closeAllPackHandles(realPack);
tmpPack.setReadOnly();
FileUtils.rename(tmpPack, realPack, StandardCopyOption.ATOMIC_MOVE);
@@ -1260,7 +1280,7 @@ private File nameFor(String name, String ext) {
}
private void checkCancelled() throws CancelledException {
- if (pm.isCancelled()) {
+ if (pm.isCancelled() || Thread.currentThread().isInterrupted()) {
throw new CancelledException(JGitText.get().operationCanceled);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
index 35049d4..e5fc0c5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
@@ -43,11 +43,6 @@
package org.eclipse.jgit.internal.storage.file;
-import org.eclipse.jgit.api.errors.JGitInternalException;
-import org.eclipse.jgit.lib.ConfigConstants;
-import org.eclipse.jgit.util.GitDateParser;
-import org.eclipse.jgit.util.SystemReader;
-
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.File;
@@ -58,6 +53,11 @@
import java.text.ParseException;
import java.time.Instant;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.util.GitDateParser;
+import org.eclipse.jgit.util.SystemReader;
+
/**
* This class manages the gc.log file for a {@link FileRepository}.
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
index bda5cbe..3f82e2a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
@@ -78,4 +78,4 @@ public AttributesNode load() throws IOException {
return r.getRules().isEmpty() ? null : r;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
index 15c5280..56f42b3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
@@ -385,7 +385,7 @@ public void close() throws IOException {
};
}
- private void requireLock() {
+ void requireLock() {
if (os == null) {
unlock();
throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotHeld, ref));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
index 6ae559a..99dcc82 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
@@ -220,6 +220,16 @@ public ObjectDirectoryInserter newInserter() {
return new ObjectDirectoryInserter(this, config);
}
+ /**
+ * Create a new inserter that inserts all objects as pack files, not loose
+ * objects.
+ *
+ * @return new inserter.
+ */
+ public PackInserter newPackInserter() {
+ return new PackInserter(this);
+ }
+
@Override
public void close() {
unpackedObjectCache.clear();
@@ -345,6 +355,9 @@ boolean hasPackedObject(AnyObjectId objectId) {
// The hasObject call should have only touched the index,
// so any failure here indicates the index is unreadable
// by this process, and the pack is likewise not readable.
+ LOG.warn(MessageFormat.format(
+ JGitText.get().unableToReadPackfile,
+ p.getPackFile().getAbsolutePath()), e);
removePack(p);
}
}
@@ -637,6 +650,8 @@ private void handlePackError(IOException e, PackFile p) {
if ((e instanceof CorruptObjectException)
|| (e instanceof PackInvalidException)) {
warnTmpl = JGitText.get().corruptPack;
+ LOG.warn(MessageFormat.format(warnTmpl,
+ p.getPackFile().getAbsolutePath()), e);
// Assume the pack is corrupted, and remove it from the list.
removePack(p);
} else if (e instanceof FileNotFoundException) {
@@ -666,8 +681,8 @@ private void handlePackError(IOException e, PackFile p) {
// Don't remove the pack from the list, as the error may be
// transient.
LOG.error(MessageFormat.format(errTmpl,
- p.getPackFile().getAbsolutePath()),
- Integer.valueOf(transientErrorCount), e);
+ p.getPackFile().getAbsolutePath(),
+ Integer.valueOf(transientErrorCount)), e);
}
}
}
@@ -814,8 +829,6 @@ private void insertPack(final PackFile pf) {
final PackFile[] oldList = o.packs;
final String name = pf.getPackFile().getName();
for (PackFile p : oldList) {
- if (PackFile.SORT.compare(pf, p) < 0)
- break;
if (name.equals(p.getPackFile().getName()))
return;
}
@@ -971,6 +984,21 @@ private Set<String> listPackDirectory() {
return nameSet;
}
+ void closeAllPackHandles(File packFile) {
+ // if the packfile already exists (because we are rewriting a
+ // packfile for the same set of objects maybe with different
+ // PackConfig) then make sure we get rid of all handles on the file.
+ // Windows will not allow for rename otherwise.
+ if (packFile.exists()) {
+ for (PackFile p : getPacks()) {
+ if (packFile.getPath().equals(p.getPackFile().getPath())) {
+ p.close();
+ break;
+ }
+ }
+ }
+ }
+
AlternateHandle[] myAlternates() {
AlternateHandle[] alt = alternates.get();
if (alt == null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
index e7db11d..e9dbcaa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
@@ -141,7 +141,7 @@ public int compare(final PackFile a, final PackFile b) {
private byte[] packChecksum;
- private PackIndex loadedIdx;
+ private volatile PackIndex loadedIdx;
private PackReverseIndex reverseIdx;
@@ -177,33 +177,44 @@ public PackFile(final File packFile, int extensions) {
length = Long.MAX_VALUE;
}
- private synchronized PackIndex idx() throws IOException {
- if (loadedIdx == null) {
- if (invalid)
- throw new PackInvalidException(packFile);
+ private PackIndex idx() throws IOException {
+ PackIndex idx = loadedIdx;
+ if (idx == null) {
+ synchronized (this) {
+ idx = loadedIdx;
+ if (idx == null) {
+ if (invalid) {
+ throw new PackInvalidException(packFile);
+ }
+ try {
+ idx = PackIndex.open(extFile(INDEX));
- try {
- final PackIndex idx = PackIndex.open(extFile(INDEX));
-
- if (packChecksum == null) {
- packChecksum = idx.packChecksum;
- } else if (!Arrays.equals(packChecksum, idx.packChecksum)) {
- throw new PackMismatchException(MessageFormat.format(
- JGitText.get().packChecksumMismatch,
- packFile.getPath()));
+ if (packChecksum == null) {
+ packChecksum = idx.packChecksum;
+ } else if (!Arrays.equals(packChecksum,
+ idx.packChecksum)) {
+ throw new PackMismatchException(MessageFormat
+ .format(JGitText.get().packChecksumMismatch,
+ packFile.getPath(),
+ ObjectId.fromRaw(packChecksum)
+ .name(),
+ ObjectId.fromRaw(idx.packChecksum)
+ .name()));
+ }
+ loadedIdx = idx;
+ } catch (InterruptedIOException e) {
+ // don't invalidate the pack, we are interrupted from
+ // another thread
+ throw e;
+ } catch (IOException e) {
+ invalid = true;
+ throw e;
+ }
}
- loadedIdx = idx;
- } catch (InterruptedIOException e) {
- // don't invalidate the pack, we are interrupted from another thread
- throw e;
- } catch (IOException e) {
- invalid = true;
- throw e;
}
}
- return loadedIdx;
+ return idx;
}
-
/** @return the File object which locates this pack on disk. */
public File getPackFile() {
return packFile;
@@ -772,10 +783,10 @@ private void onOpenPack() throws IOException {
fd.readFully(buf, 0, 20);
if (!Arrays.equals(buf, packChecksum)) {
throw new PackMismatchException(MessageFormat.format(
- JGitText.get().packObjectCountMismatch
- , ObjectId.fromRaw(buf).name()
- , ObjectId.fromRaw(idx.packChecksum).name()
- , getPackFile()));
+ JGitText.get().packChecksumMismatch,
+ getPackFile(),
+ ObjectId.fromRaw(buf).name(),
+ ObjectId.fromRaw(idx.packChecksum).name()));
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java
index 154809b..962f765 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java
@@ -82,4 +82,4 @@ public int read() throws IOException {
public void close() {
wc.close();
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java
new file mode 100644
index 0000000..ff959e8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java
@@ -0,0 +1,708 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.file;
+
+import static java.nio.file.StandardCopyOption.ATOMIC_MOVE;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import static org.eclipse.jgit.lib.Constants.OBJ_OFS_DELTA;
+import static org.eclipse.jgit.lib.Constants.OBJ_REF_DELTA;
+
+import java.io.BufferedInputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.channels.Channels;
+import java.text.MessageFormat;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.CRC32;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.InflaterCache;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdOwnerMap;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.ObjectStream;
+import org.eclipse.jgit.transport.PackParser;
+import org.eclipse.jgit.transport.PackedObjectInfo;
+import org.eclipse.jgit.util.BlockList;
+import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.io.CountingOutputStream;
+import org.eclipse.jgit.util.sha1.SHA1;
+
+/**
+ * Object inserter that inserts one pack per call to {@link #flush()}, and never
+ * inserts loose objects.
+ */
+public class PackInserter extends ObjectInserter {
+ /** Always produce version 2 indexes, to get CRC data. */
+ private static final int INDEX_VERSION = 2;
+
+ private final ObjectDirectory db;
+
+ private List<PackedObjectInfo> objectList;
+ private ObjectIdOwnerMap<PackedObjectInfo> objectMap;
+ private boolean rollback;
+ private boolean checkExisting = true;
+
+ private int compression = Deflater.BEST_COMPRESSION;
+ private File tmpPack;
+ private PackStream packOut;
+ private Inflater cachedInflater;
+
+ PackInserter(ObjectDirectory db) {
+ this.db = db;
+ }
+
+ /**
+ * @param check
+ * if false, will write out possibly-duplicate objects without
+ * first checking whether they exist in the repo; default is true.
+ */
+ public void checkExisting(boolean check) {
+ checkExisting = check;
+ }
+
+ /**
+ * @param compression
+ * compression level for zlib deflater.
+ */
+ public void setCompressionLevel(int compression) {
+ this.compression = compression;
+ }
+
+ int getBufferSize() {
+ return buffer().length;
+ }
+
+ @Override
+ public ObjectId insert(int type, byte[] data, int off, int len)
+ throws IOException {
+ ObjectId id = idFor(type, data, off, len);
+ if (objectMap != null && objectMap.contains(id)) {
+ return id;
+ }
+ // Ignore loose objects, which are potentially unreachable.
+ if (checkExisting && db.hasPackedObject(id)) {
+ return id;
+ }
+
+ long offset = beginObject(type, len);
+ packOut.compress.write(data, off, len);
+ packOut.compress.finish();
+ return endObject(id, offset);
+ }
+
+ @Override
+ public ObjectId insert(int type, long len, InputStream in)
+ throws IOException {
+ byte[] buf = buffer();
+ if (len <= buf.length) {
+ IO.readFully(in, buf, 0, (int) len);
+ return insert(type, buf, 0, (int) len);
+ }
+
+ long offset = beginObject(type, len);
+ SHA1 md = digest();
+ md.update(Constants.encodedTypeString(type));
+ md.update((byte) ' ');
+ md.update(Constants.encodeASCII(len));
+ md.update((byte) 0);
+
+ while (0 < len) {
+ int n = in.read(buf, 0, (int) Math.min(buf.length, len));
+ if (n <= 0) {
+ throw new EOFException();
+ }
+ md.update(buf, 0, n);
+ packOut.compress.write(buf, 0, n);
+ len -= n;
+ }
+ packOut.compress.finish();
+ return endObject(md.toObjectId(), offset);
+ }
+
+ private long beginObject(int type, long len) throws IOException {
+ if (packOut == null) {
+ beginPack();
+ }
+ long offset = packOut.getOffset();
+ packOut.beginObject(type, len);
+ return offset;
+ }
+
+ private ObjectId endObject(ObjectId id, long offset) {
+ PackedObjectInfo obj = new PackedObjectInfo(id);
+ obj.setOffset(offset);
+ obj.setCRC((int) packOut.crc32.getValue());
+ objectList.add(obj);
+ objectMap.addIfAbsent(obj);
+ return id;
+ }
+
+ private static File idxFor(File packFile) {
+ String p = packFile.getName();
+ return new File(
+ packFile.getParentFile(),
+ p.substring(0, p.lastIndexOf('.')) + ".idx"); //$NON-NLS-1$
+ }
+
+ private void beginPack() throws IOException {
+ objectList = new BlockList<>();
+ objectMap = new ObjectIdOwnerMap<>();
+
+ rollback = true;
+ tmpPack = File.createTempFile("insert_", ".pack", db.getDirectory()); //$NON-NLS-1$ //$NON-NLS-2$
+ packOut = new PackStream(tmpPack);
+
+ // Write the header as though it were a single object pack.
+ packOut.write(packOut.hdrBuf, 0, writePackHeader(packOut.hdrBuf, 1));
+ }
+
+ private static int writePackHeader(byte[] buf, int objectCount) {
+ System.arraycopy(Constants.PACK_SIGNATURE, 0, buf, 0, 4);
+ NB.encodeInt32(buf, 4, 2); // Always use pack version 2.
+ NB.encodeInt32(buf, 8, objectCount);
+ return 12;
+ }
+
+ @Override
+ public PackParser newPackParser(InputStream in) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ObjectReader newReader() {
+ return new Reader();
+ }
+
+ @Override
+ public void flush() throws IOException {
+ if (tmpPack == null) {
+ return;
+ }
+
+ if (packOut == null) {
+ throw new IOException();
+ }
+
+ byte[] packHash;
+ try {
+ packHash = packOut.finishPack();
+ } finally {
+ packOut = null;
+ }
+
+ Collections.sort(objectList);
+ File tmpIdx = idxFor(tmpPack);
+ writePackIndex(tmpIdx, packHash, objectList);
+
+ File realPack = new File(
+ new File(db.getDirectory(), "pack"), //$NON-NLS-1$
+ "pack-" + computeName(objectList).name() + ".pack"); //$NON-NLS-1$ //$NON-NLS-2$
+ db.closeAllPackHandles(realPack);
+ tmpPack.setReadOnly();
+ FileUtils.rename(tmpPack, realPack, ATOMIC_MOVE);
+
+ File realIdx = idxFor(realPack);
+ tmpIdx.setReadOnly();
+ try {
+ FileUtils.rename(tmpIdx, realIdx, ATOMIC_MOVE);
+ } catch (IOException e) {
+ File newIdx = new File(
+ realIdx.getParentFile(), realIdx.getName() + ".new"); //$NON-NLS-1$
+ try {
+ FileUtils.rename(tmpIdx, newIdx, ATOMIC_MOVE);
+ } catch (IOException e2) {
+ newIdx = tmpIdx;
+ e = e2;
+ }
+ throw new IOException(MessageFormat.format(
+ JGitText.get().panicCantRenameIndexFile, newIdx,
+ realIdx), e);
+ }
+
+ db.openPack(realPack);
+ rollback = false;
+ clear();
+ }
+
+ private static void writePackIndex(File idx, byte[] packHash,
+ List<PackedObjectInfo> list) throws IOException {
+ try (OutputStream os = new FileOutputStream(idx)) {
+ PackIndexWriter w = PackIndexWriter.createVersion(os, INDEX_VERSION);
+ w.write(list, packHash);
+ }
+ }
+
+ private ObjectId computeName(List<PackedObjectInfo> list) {
+ SHA1 md = digest().reset();
+ byte[] buf = buffer();
+ for (PackedObjectInfo otp : list) {
+ otp.copyRawTo(buf, 0);
+ md.update(buf, 0, OBJECT_ID_LENGTH);
+ }
+ return ObjectId.fromRaw(md.digest());
+ }
+
+ @Override
+ public void close() {
+ try {
+ if (packOut != null) {
+ try {
+ packOut.close();
+ } catch (IOException err) {
+ // Ignore a close failure, the pack should be removed.
+ }
+ }
+ if (rollback && tmpPack != null) {
+ try {
+ FileUtils.delete(tmpPack);
+ } catch (IOException e) {
+ // Still delete idx.
+ }
+ try {
+ FileUtils.delete(idxFor(tmpPack));
+ } catch (IOException e) {
+ // Ignore error deleting temp idx.
+ }
+ rollback = false;
+ }
+ } finally {
+ clear();
+ try {
+ InflaterCache.release(cachedInflater);
+ } finally {
+ cachedInflater = null;
+ }
+ }
+ }
+
+ private void clear() {
+ objectList = null;
+ objectMap = null;
+ tmpPack = null;
+ packOut = null;
+ }
+
+ private Inflater inflater() {
+ if (cachedInflater == null) {
+ cachedInflater = InflaterCache.get();
+ } else {
+ cachedInflater.reset();
+ }
+ return cachedInflater;
+ }
+
+ /**
+ * Stream that writes to a pack file.
+ * <p>
+ * Backed by two views of the same open file descriptor: a random-access file,
+ * and an output stream. Seeking in the file causes subsequent writes to the
+ * output stream to occur wherever the file pointer is pointing, so we need to
+ * take care to always seek to the end of the file before writing a new
+ * object.
+ * <p>
+ * Callers should always use {@link #seek(long)} to seek, rather than reaching
+ * into the file member. As long as this contract is followed, calls to {@link
+ * #write(byte[], int, int)} are guaranteed to write at the end of the file,
+ * even if there have been intermediate seeks.
+ */
+ private class PackStream extends OutputStream {
+ final byte[] hdrBuf;
+ final CRC32 crc32;
+ final DeflaterOutputStream compress;
+
+ private final RandomAccessFile file;
+ private final CountingOutputStream out;
+ private final Deflater deflater;
+
+ private boolean atEnd;
+
+ PackStream(File pack) throws IOException {
+ file = new RandomAccessFile(pack, "rw"); //$NON-NLS-1$
+ out = new CountingOutputStream(new FileOutputStream(file.getFD()));
+ deflater = new Deflater(compression);
+ compress = new DeflaterOutputStream(this, deflater, 8192);
+ hdrBuf = new byte[32];
+ crc32 = new CRC32();
+ atEnd = true;
+ }
+
+ long getOffset() {
+ // This value is accurate as long as we only ever write to the end of the
+ // file, and don't seek back to overwrite any previous segments. Although
+ // this is subtle, storing the stream counter this way is still preferable
+ // to returning file.length() here, as it avoids a syscall and possible
+ // IOException.
+ return out.getCount();
+ }
+
+ void seek(long offset) throws IOException {
+ file.seek(offset);
+ atEnd = false;
+ }
+
+ void beginObject(int objectType, long length) throws IOException {
+ crc32.reset();
+ deflater.reset();
+ write(hdrBuf, 0, encodeTypeSize(objectType, length));
+ }
+
+ private int encodeTypeSize(int type, long rawLength) {
+ long nextLength = rawLength >>> 4;
+ hdrBuf[0] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (type << 4) | (rawLength & 0x0F));
+ rawLength = nextLength;
+ int n = 1;
+ while (rawLength > 0) {
+ nextLength >>>= 7;
+ hdrBuf[n++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (rawLength & 0x7F));
+ rawLength = nextLength;
+ }
+ return n;
+ }
+
+ @Override
+ public void write(final int b) throws IOException {
+ hdrBuf[0] = (byte) b;
+ write(hdrBuf, 0, 1);
+ }
+
+ @Override
+ public void write(byte[] data, int off, int len) throws IOException {
+ crc32.update(data, off, len);
+ if (!atEnd) {
+ file.seek(file.length());
+ atEnd = true;
+ }
+ out.write(data, off, len);
+ }
+
+ byte[] finishPack() throws IOException {
+ // Overwrite placeholder header with actual object count, then hash. This
+ // method intentionally uses direct seek/write calls rather than the
+ // wrappers which keep track of atEnd. This leaves atEnd, the file
+ // pointer, and out's counter in an inconsistent state; that's ok, since
+ // this method closes the file anyway.
+ try {
+ file.seek(0);
+ out.write(hdrBuf, 0, writePackHeader(hdrBuf, objectList.size()));
+
+ byte[] buf = buffer();
+ SHA1 md = digest().reset();
+ file.seek(0);
+ while (true) {
+ int r = file.read(buf);
+ if (r < 0) {
+ break;
+ }
+ md.update(buf, 0, r);
+ }
+ byte[] packHash = md.digest();
+ out.write(packHash, 0, packHash.length);
+ return packHash;
+ } finally {
+ close();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ deflater.end();
+ try {
+ out.close();
+ } finally {
+ file.close();
+ }
+ }
+
+ byte[] inflate(long filePos, int len) throws IOException, DataFormatException {
+ byte[] dstbuf;
+ try {
+ dstbuf = new byte[len];
+ } catch (OutOfMemoryError noMemory) {
+ return null; // Caller will switch to large object streaming.
+ }
+
+ byte[] srcbuf = buffer();
+ Inflater inf = inflater();
+ filePos += setInput(filePos, inf, srcbuf);
+ for (int dstoff = 0;;) {
+ int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff);
+ dstoff += n;
+ if (inf.finished()) {
+ return dstbuf;
+ }
+ if (inf.needsInput()) {
+ filePos += setInput(filePos, inf, srcbuf);
+ } else if (n == 0) {
+ throw new DataFormatException();
+ }
+ }
+ }
+
+ private int setInput(long filePos, Inflater inf, byte[] buf)
+ throws IOException {
+ if (file.getFilePointer() != filePos) {
+ seek(filePos);
+ }
+ int n = file.read(buf);
+ if (n < 0) {
+ throw new EOFException(JGitText.get().unexpectedEofInPack);
+ }
+ inf.setInput(buf, 0, n);
+ return n;
+ }
+ }
+
+ private class Reader extends ObjectReader {
+ private final ObjectReader ctx;
+
+ private Reader() {
+ ctx = db.newReader();
+ setStreamFileThreshold(ctx.getStreamFileThreshold());
+ }
+
+ @Override
+ public ObjectReader newReader() {
+ return db.newReader();
+ }
+
+ @Override
+ public ObjectInserter getCreatedFromInserter() {
+ return PackInserter.this;
+ }
+
+ @Override
+ public Collection<ObjectId> resolve(AbbreviatedObjectId id)
+ throws IOException {
+ Collection<ObjectId> stored = ctx.resolve(id);
+ if (objectList == null) {
+ return stored;
+ }
+
+ Set<ObjectId> r = new HashSet<>(stored.size() + 2);
+ r.addAll(stored);
+ for (PackedObjectInfo obj : objectList) {
+ if (id.prefixCompare(obj) == 0) {
+ r.add(obj.copy());
+ }
+ }
+ return r;
+ }
+
+ @Override
+ public ObjectLoader open(AnyObjectId objectId, int typeHint)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ if (objectMap == null) {
+ return ctx.open(objectId, typeHint);
+ }
+
+ PackedObjectInfo obj = objectMap.get(objectId);
+ if (obj == null) {
+ return ctx.open(objectId, typeHint);
+ }
+
+ byte[] buf = buffer();
+ packOut.seek(obj.getOffset());
+ int cnt = packOut.file.read(buf, 0, 20);
+ if (cnt <= 0) {
+ throw new EOFException(JGitText.get().unexpectedEofInPack);
+ }
+
+ int c = buf[0] & 0xff;
+ int type = (c >> 4) & 7;
+ if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) {
+ throw new IOException(MessageFormat.format(
+ JGitText.get().cannotReadBackDelta, Integer.toString(type)));
+ }
+ if (typeHint != OBJ_ANY && type != typeHint) {
+ throw new IncorrectObjectTypeException(objectId.copy(), typeHint);
+ }
+
+ long sz = c & 0x0f;
+ int ptr = 1;
+ int shift = 4;
+ while ((c & 0x80) != 0) {
+ if (ptr >= cnt) {
+ throw new EOFException(JGitText.get().unexpectedEofInPack);
+ }
+ c = buf[ptr++] & 0xff;
+ sz += ((long) (c & 0x7f)) << shift;
+ shift += 7;
+ }
+
+ long zpos = obj.getOffset() + ptr;
+ if (sz < getStreamFileThreshold()) {
+ byte[] data = inflate(obj, zpos, (int) sz);
+ if (data != null) {
+ return new ObjectLoader.SmallObject(type, data);
+ }
+ }
+ return new StreamLoader(type, sz, zpos);
+ }
+
+ private byte[] inflate(PackedObjectInfo obj, long zpos, int sz)
+ throws IOException, CorruptObjectException {
+ try {
+ return packOut.inflate(zpos, sz);
+ } catch (DataFormatException dfe) {
+ CorruptObjectException coe = new CorruptObjectException(
+ MessageFormat.format(
+ JGitText.get().objectAtHasBadZlibStream,
+ Long.valueOf(obj.getOffset()),
+ tmpPack.getAbsolutePath()));
+ coe.initCause(dfe);
+ throw coe;
+ }
+ }
+
+ @Override
+ public Set<ObjectId> getShallowCommits() throws IOException {
+ return ctx.getShallowCommits();
+ }
+
+ @Override
+ public void close() {
+ ctx.close();
+ }
+
+ private class StreamLoader extends ObjectLoader {
+ private final int type;
+ private final long size;
+ private final long pos;
+
+ StreamLoader(int type, long size, long pos) {
+ this.type = type;
+ this.size = size;
+ this.pos = pos;
+ }
+
+ @Override
+ public ObjectStream openStream()
+ throws MissingObjectException, IOException {
+ int bufsz = buffer().length;
+ packOut.seek(pos);
+
+ InputStream fileStream = new FilterInputStream(
+ Channels.newInputStream(packOut.file.getChannel())) {
+ // atEnd was already set to false by the previous seek, but it's
+ // technically possible for a caller to call insert on the
+ // inserter in the middle of reading from this stream. Behavior is
+ // undefined in this case, so it would arguably be ok to ignore,
+ // but it's not hard to at least make an attempt to not corrupt
+ // the data.
+ @Override
+ public int read() throws IOException {
+ packOut.atEnd = false;
+ return super.read();
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ packOut.atEnd = false;
+ return super.read(b);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ packOut.atEnd = false;
+ return super.read(b,off,len);
+ }
+
+ @Override
+ public void close() {
+ // Never close underlying RandomAccessFile, which lasts the
+ // lifetime of the enclosing PackStream.
+ }
+ };
+ return new ObjectStream.Filter(
+ type, size,
+ new BufferedInputStream(
+ new InflaterInputStream(fileStream, inflater(), bufsz), bufsz));
+ }
+
+ @Override
+ public int getType() {
+ return type;
+ }
+
+ @Override
+ public long getSize() {
+ return size;
+ }
+
+ @Override
+ public byte[] getCachedBytes() throws LargeObjectException {
+ throw new LargeObjectException.ExceedsLimit(
+ getStreamFileThreshold(), size);
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
new file mode 100644
index 0000000..c1f5476
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.file;
+
+import static java.util.stream.Collectors.toList;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.LockFailedException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.RefDirectory.PackedRefList;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.util.RefList;
+
+/**
+ * Implementation of {@link BatchRefUpdate} that uses the {@code packed-refs}
+ * file to support atomically updating multiple refs.
+ * <p>
+ * The algorithm is designed to be compatible with traditional single ref
+ * updates operating on single refs only. Regardless of success or failure, the
+ * results are atomic: from the perspective of any reader, either all updates in
+ * the batch will be visible, or none will. In the case of process failure
+ * during any of the following steps, removal of stale lock files is always
+ * safe, and will never result in an inconsistent state, although the update may
+ * or may not have been applied.
+ * <p>
+ * The algorithm is:
+ * <ol>
+ * <li>Pack loose refs involved in the transaction using the normal pack-refs
+ * operation. This ensures that creating lock files in the following step
+ * succeeds even if a batch contains both a delete of {@code refs/x} (loose) and
+ * a create of {@code refs/x/y}.</li>
+ * <li>Create locks for all loose refs involved in the transaction, even if they
+ * are not currently loose.</li>
+ * <li>Pack loose refs again, this time while holding all lock files (see {@link
+ * RefDirectory#pack(Map)}), without deleting them afterwards. This covers a
+ * potential race where new loose refs were created after the initial packing
+ * step. If no new loose refs were created during this race, this step does not
+ * modify any files on disk. Keep the merged state in memory.</li>
+ * <li>Update the in-memory packed refs with the commands in the batch, possibly
+ * failing the whole batch if any old ref values do not match.</li>
+ * <li>If the update succeeds, lock {@code packed-refs} and commit by atomically
+ * renaming the lock file.</li>
+ * <li>Delete loose ref lock files.</li>
+ * </ol>
+ *
+ * Because the packed-refs file format is a sorted list, this algorithm is
+ * linear in the total number of refs, regardless of the batch size. This can be
+ * a significant slowdown on repositories with large numbers of refs; callers
+ * that prefer speed over atomicity should use {@code setAtomic(false)}. As an
+ * optimization, an update containing a single ref update does not use the
+ * packed-refs protocol.
+ */
+class PackedBatchRefUpdate extends BatchRefUpdate {
+ private RefDirectory refdb;
+
+ PackedBatchRefUpdate(RefDirectory refdb) {
+ super(refdb);
+ this.refdb = refdb;
+ }
+
+ @Override
+ public void execute(RevWalk walk, ProgressMonitor monitor,
+ List<String> options) throws IOException {
+ if (!isAtomic()) {
+ // Use default one-by-one implementation.
+ super.execute(walk, monitor, options);
+ return;
+ }
+ List<ReceiveCommand> pending =
+ ReceiveCommand.filter(getCommands(), NOT_ATTEMPTED);
+ if (pending.isEmpty()) {
+ return;
+ }
+ if (pending.size() == 1) {
+ // Single-ref updates are always atomic, no need for packed-refs.
+ super.execute(walk, monitor, options);
+ return;
+ }
+
+ // Required implementation details copied from super.execute.
+ if (!blockUntilTimestamps(MAX_WAIT)) {
+ return;
+ }
+ if (options != null) {
+ setPushOptions(options);
+ }
+ // End required implementation details.
+
+ // Check for conflicting names before attempting to acquire locks, since
+ // lockfile creation may fail on file/directory conflicts.
+ if (!checkConflictingNames(pending)) {
+ return;
+ }
+
+ if (!checkObjectExistence(walk, pending)) {
+ return;
+ }
+
+ if (!checkNonFastForwards(walk, pending)) {
+ return;
+ }
+
+ // Pack refs normally, so we can create lock files even in the case where
+ // refs/x is deleted and refs/x/y is created in this batch.
+ try {
+ refdb.pack(
+ pending.stream().map(ReceiveCommand::getRefName).collect(toList()));
+ } catch (LockFailedException e) {
+ lockFailure(pending.get(0), pending);
+ return;
+ }
+
+ Map<String, LockFile> locks = null;
+ refdb.inProcessPackedRefsLock.lock();
+ try {
+ PackedRefList oldPackedList;
+ if (!refdb.isInClone()) {
+ locks = lockLooseRefs(pending);
+ if (locks == null) {
+ return;
+ }
+ oldPackedList = refdb.pack(locks);
+ } else {
+ // During clone locking isn't needed since no refs exist yet.
+ // This also helps to avoid problems with refs only differing in
+ // case on a case insensitive filesystem (bug 528497)
+ oldPackedList = refdb.getPackedRefs();
+ }
+ RefList<Ref> newRefs = applyUpdates(walk, oldPackedList, pending);
+ if (newRefs == null) {
+ return;
+ }
+ LockFile packedRefsLock = refdb.lockPackedRefs();
+ if (packedRefsLock == null) {
+ lockFailure(pending.get(0), pending);
+ return;
+ }
+ // commitPackedRefs removes lock file (by renaming over real file).
+ refdb.commitPackedRefs(packedRefsLock, newRefs, oldPackedList,
+ true);
+ } finally {
+ try {
+ unlockAll(locks);
+ } finally {
+ refdb.inProcessPackedRefsLock.unlock();
+ }
+ }
+
+ refdb.fireRefsChanged();
+ pending.forEach(c -> c.setResult(ReceiveCommand.Result.OK));
+ writeReflog(pending);
+ }
+
+ private boolean checkConflictingNames(List<ReceiveCommand> commands)
+ throws IOException {
+ Set<String> takenNames = new HashSet<>();
+ Set<String> takenPrefixes = new HashSet<>();
+ Set<String> deletes = new HashSet<>();
+ for (ReceiveCommand cmd : commands) {
+ if (cmd.getType() != ReceiveCommand.Type.DELETE) {
+ takenNames.add(cmd.getRefName());
+ addPrefixesTo(cmd.getRefName(), takenPrefixes);
+ } else {
+ deletes.add(cmd.getRefName());
+ }
+ }
+ Set<String> initialRefs = refdb.getRefs(RefDatabase.ALL).keySet();
+ for (String name : initialRefs) {
+ if (!deletes.contains(name)) {
+ takenNames.add(name);
+ addPrefixesTo(name, takenPrefixes);
+ }
+ }
+
+ for (ReceiveCommand cmd : commands) {
+ if (cmd.getType() != ReceiveCommand.Type.DELETE &&
+ takenPrefixes.contains(cmd.getRefName())) {
+ // This ref is a prefix of some other ref. This check doesn't apply when
+ // this command is a delete, because if the ref is deleted nobody will
+ // ever be creating a loose ref with that name.
+ lockFailure(cmd, commands);
+ return false;
+ }
+ for (String prefix : getPrefixes(cmd.getRefName())) {
+ if (takenNames.contains(prefix)) {
+ // A prefix of this ref is already a refname. This check does apply
+ // when this command is a delete, because we would need to create the
+ // refname as a directory in order to create a lockfile for the
+ // to-be-deleted ref.
+ lockFailure(cmd, commands);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean checkObjectExistence(RevWalk walk,
+ List<ReceiveCommand> commands) throws IOException {
+ for (ReceiveCommand cmd : commands) {
+ try {
+ if (!cmd.getNewId().equals(ObjectId.zeroId())) {
+ walk.parseAny(cmd.getNewId());
+ }
+ } catch (MissingObjectException e) {
+ // ReceiveCommand#setResult(Result) converts REJECTED to
+ // REJECTED_NONFASTFORWARD, even though that result is also used for a
+ // missing object. Eagerly handle this case so we can set the right
+ // result.
+ reject(cmd, ReceiveCommand.Result.REJECTED_MISSING_OBJECT, commands);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean checkNonFastForwards(RevWalk walk,
+ List<ReceiveCommand> commands) throws IOException {
+ if (isAllowNonFastForwards()) {
+ return true;
+ }
+ for (ReceiveCommand cmd : commands) {
+ cmd.updateType(walk);
+ if (cmd.getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD) {
+ reject(cmd, REJECTED_NONFASTFORWARD, commands);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Lock loose refs corresponding to a list of commands.
+ *
+ * @param commands
+ * commands that we intend to execute.
+ * @return map of ref name in the input commands to lock file. Always contains
+ * one entry for each ref in the input list. All locks are acquired
+ * before returning. If any lock was not able to be acquired: the
+ * return value is null; no locks are held; and all commands that were
+ * pending are set to fail with {@code LOCK_FAILURE}.
+ * @throws IOException
+ * an error occurred other than a failure to acquire; no locks are
+ * held if this exception is thrown.
+ */
+ @Nullable
+ private Map<String, LockFile> lockLooseRefs(List<ReceiveCommand> commands)
+ throws IOException {
+ ReceiveCommand failed = null;
+ Map<String, LockFile> locks = new HashMap<>();
+ try {
+ RETRY: for (int ms : refdb.getRetrySleepMs()) {
+ failed = null;
+ // Release all locks before trying again, to prevent deadlock.
+ unlockAll(locks);
+ locks.clear();
+ RefDirectory.sleep(ms);
+
+ for (ReceiveCommand c : commands) {
+ String name = c.getRefName();
+ LockFile lock = new LockFile(refdb.fileFor(name));
+ if (locks.put(name, lock) != null) {
+ throw new IOException(
+ MessageFormat.format(JGitText.get().duplicateRef, name));
+ }
+ if (!lock.lock()) {
+ failed = c;
+ continue RETRY;
+ }
+ }
+ Map<String, LockFile> result = locks;
+ locks = null;
+ return result;
+ }
+ } finally {
+ unlockAll(locks);
+ }
+ lockFailure(failed != null ? failed : commands.get(0), commands);
+ return null;
+ }
+
+ private static RefList<Ref> applyUpdates(RevWalk walk, RefList<Ref> refs,
+ List<ReceiveCommand> commands) throws IOException {
+ int nDeletes = 0;
+ List<ReceiveCommand> adds = new ArrayList<>(commands.size());
+ for (ReceiveCommand c : commands) {
+ if (c.getType() == ReceiveCommand.Type.CREATE) {
+ adds.add(c);
+ } else if (c.getType() == ReceiveCommand.Type.DELETE) {
+ nDeletes++;
+ }
+ }
+ int addIdx = 0;
+
+ // Construct a new RefList by linearly scanning the old list, and merging in
+ // any updates.
+ Map<String, ReceiveCommand> byName = byName(commands);
+ RefList.Builder<Ref> b =
+ new RefList.Builder<>(refs.size() - nDeletes + adds.size());
+ for (Ref ref : refs) {
+ String name = ref.getName();
+ ReceiveCommand cmd = byName.remove(name);
+ if (cmd == null) {
+ b.add(ref);
+ continue;
+ }
+ if (!cmd.getOldId().equals(ref.getObjectId())) {
+ lockFailure(cmd, commands);
+ return null;
+ }
+
+ // Consume any adds between the last and current ref.
+ while (addIdx < adds.size()) {
+ ReceiveCommand currAdd = adds.get(addIdx);
+ if (currAdd.getRefName().compareTo(name) < 0) {
+ b.add(peeledRef(walk, currAdd));
+ byName.remove(currAdd.getRefName());
+ } else {
+ break;
+ }
+ addIdx++;
+ }
+
+ if (cmd.getType() != ReceiveCommand.Type.DELETE) {
+ b.add(peeledRef(walk, cmd));
+ }
+ }
+
+ // All remaining adds are valid, since the refs didn't exist.
+ while (addIdx < adds.size()) {
+ ReceiveCommand cmd = adds.get(addIdx++);
+ byName.remove(cmd.getRefName());
+ b.add(peeledRef(walk, cmd));
+ }
+
+ // Any remaining updates/deletes do not correspond to any existing refs, so
+ // they are lock failures.
+ if (!byName.isEmpty()) {
+ lockFailure(byName.values().iterator().next(), commands);
+ return null;
+ }
+
+ return b.toRefList();
+ }
+
+ private void writeReflog(List<ReceiveCommand> commands) {
+ PersonIdent ident = getRefLogIdent();
+ if (ident == null) {
+ ident = new PersonIdent(refdb.getRepository());
+ }
+ for (ReceiveCommand cmd : commands) {
+ // Assume any pending commands have already been executed atomically.
+ if (cmd.getResult() != ReceiveCommand.Result.OK) {
+ continue;
+ }
+ String name = cmd.getRefName();
+
+ if (cmd.getType() == ReceiveCommand.Type.DELETE) {
+ try {
+ RefDirectory.delete(refdb.logFor(name), RefDirectory.levelsIn(name));
+ } catch (IOException e) {
+ // Ignore failures, see below.
+ }
+ continue;
+ }
+
+ if (isRefLogDisabled(cmd)) {
+ continue;
+ }
+
+ String msg = getRefLogMessage(cmd);
+ if (isRefLogIncludingResult(cmd)) {
+ String strResult = toResultString(cmd);
+ if (strResult != null) {
+ msg = msg.isEmpty()
+ ? strResult : msg + ": " + strResult; //$NON-NLS-1$
+ }
+ }
+ try {
+ new ReflogWriter(refdb, isForceRefLog(cmd))
+ .log(name, cmd.getOldId(), cmd.getNewId(), ident, msg);
+ } catch (IOException e) {
+ // Ignore failures, but continue attempting to write more reflogs.
+ //
+ // In this storage format, it is impossible to atomically write the
+ // reflog with the ref updates, so we have to choose between:
+ // a. Propagating this exception and claiming failure, even though the
+ // actual ref updates succeeded.
+ // b. Ignoring failures writing the reflog, so we claim success if and
+ // only if the ref updates succeeded.
+ // We choose (b) in order to surprise callers the least.
+ //
+ // Possible future improvements:
+ // * Log a warning to a logger.
+ // * Retry a fixed number of times in case the error was transient.
+ }
+ }
+ }
+
+ private String toResultString(ReceiveCommand cmd) {
+ switch (cmd.getType()) {
+ case CREATE:
+ return ReflogEntry.PREFIX_CREATED;
+ case UPDATE:
+ // Match the behavior of a single RefUpdate. In that case, setting the
+ // force bit completely bypasses the potentially expensive isMergedInto
+ // check, by design, so the reflog message may be inaccurate.
+ //
+ // Similarly, this class bypasses the isMergedInto checks when the force
+ // bit is set, meaning we can't actually distinguish between UPDATE and
+ // UPDATE_NONFASTFORWARD when isAllowNonFastForwards() returns true.
+ return isAllowNonFastForwards()
+ ? ReflogEntry.PREFIX_FORCED_UPDATE : ReflogEntry.PREFIX_FAST_FORWARD;
+ case UPDATE_NONFASTFORWARD:
+ return ReflogEntry.PREFIX_FORCED_UPDATE;
+ default:
+ return null;
+ }
+ }
+
+ private static Map<String, ReceiveCommand> byName(
+ List<ReceiveCommand> commands) {
+ Map<String, ReceiveCommand> ret = new LinkedHashMap<>();
+ for (ReceiveCommand cmd : commands) {
+ ret.put(cmd.getRefName(), cmd);
+ }
+ return ret;
+ }
+
+ private static Ref peeledRef(RevWalk walk, ReceiveCommand cmd)
+ throws IOException {
+ ObjectId newId = cmd.getNewId().copy();
+ RevObject obj = walk.parseAny(newId);
+ if (obj instanceof RevTag) {
+ return new ObjectIdRef.PeeledTag(
+ Ref.Storage.PACKED, cmd.getRefName(), newId, walk.peel(obj).copy());
+ }
+ return new ObjectIdRef.PeeledNonTag(
+ Ref.Storage.PACKED, cmd.getRefName(), newId);
+ }
+
+ private static void unlockAll(@Nullable Map<?, LockFile> locks) {
+ if (locks != null) {
+ locks.values().forEach(LockFile::unlock);
+ }
+ }
+
+ private static void lockFailure(ReceiveCommand cmd,
+ List<ReceiveCommand> commands) {
+ reject(cmd, LOCK_FAILURE, commands);
+ }
+
+ private static void reject(ReceiveCommand cmd, ReceiveCommand.Result result,
+ List<ReceiveCommand> commands) {
+ cmd.setResult(result);
+ for (ReceiveCommand c2 : commands) {
+ if (c2.getResult() == ReceiveCommand.Result.OK) {
+ // Undo OK status so ReceiveCommand#abort aborts it. Assumes this method
+ // is always called before committing any updates to disk.
+ c2.setResult(ReceiveCommand.Result.NOT_ATTEMPTED);
+ }
+ }
+ ReceiveCommand.abort(commands);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
index 8338b2c..4003b27 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
@@ -48,6 +48,7 @@
import static org.eclipse.jgit.lib.Constants.CHARSET;
import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.LOGS;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
import static org.eclipse.jgit.lib.Constants.PACKED_REFS;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
@@ -63,19 +64,24 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.Files;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.text.MessageFormat;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.InvalidObjectIdException;
import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -138,15 +144,21 @@ public class RefDirectory extends RefDatabase {
Constants.MERGE_HEAD, Constants.FETCH_HEAD, Constants.ORIG_HEAD,
Constants.CHERRY_PICK_HEAD };
+ @SuppressWarnings("boxing")
+ private static final List<Integer> RETRY_SLEEP_MS =
+ Collections.unmodifiableList(Arrays.asList(0, 100, 200, 400, 800, 1600));
+
private final FileRepository parent;
private final File gitDir;
final File refsDir;
- private final ReflogWriter logWriter;
+ final File packedRefsFile;
- private final File packedRefsFile;
+ final File logsDir;
+
+ final File logsRefsDir;
/**
* Immutable sorted list of loose references.
@@ -162,6 +174,22 @@ public class RefDirectory extends RefDatabase {
final AtomicReference<PackedRefList> packedRefs = new AtomicReference<>();
/**
+ * Lock for coordinating operations within a single process that may contend
+ * on the {@code packed-refs} file.
+ * <p>
+ * All operations that write {@code packed-refs} must still acquire a
+ * {@link LockFile} on {@link #packedRefsFile}, even after they have acquired
+ * this lock, since there may be multiple {@link RefDirectory} instances or
+ * other processes operating on the same repo on disk.
+ * <p>
+ * This lock exists so multiple threads in the same process can wait in a fair
+ * queue without trying, failing, and retrying to acquire the on-disk lock. If
+ * {@code RepositoryCache} is used, this lock instance will be used by all
+ * threads.
+ */
+ final ReentrantLock inProcessPackedRefsLock = new ReentrantLock(true);
+
+ /**
* Number of modifications made to this database.
* <p>
* This counter is incremented when a change is made, or detected from the
@@ -177,24 +205,43 @@ public class RefDirectory extends RefDatabase {
*/
private final AtomicInteger lastNotifiedModCnt = new AtomicInteger();
+ private List<Integer> retrySleepMs = RETRY_SLEEP_MS;
+
RefDirectory(final FileRepository db) {
final FS fs = db.getFS();
parent = db;
gitDir = db.getDirectory();
- logWriter = new ReflogWriter(db);
refsDir = fs.resolve(gitDir, R_REFS);
+ logsDir = fs.resolve(gitDir, LOGS);
+ logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS);
packedRefsFile = fs.resolve(gitDir, PACKED_REFS);
looseRefs.set(RefList.<LooseRef> emptyList());
- packedRefs.set(PackedRefList.NO_PACKED_REFS);
+ packedRefs.set(NO_PACKED_REFS);
}
Repository getRepository() {
return parent;
}
- ReflogWriter getLogWriter() {
- return logWriter;
+ ReflogWriter newLogWriter(boolean force) {
+ return new ReflogWriter(this, force);
+ }
+
+ /**
+ * Locate the log file on disk for a single reference name.
+ *
+ * @param name
+ * name of the ref, relative to the Git repository top level
+ * directory (so typically starts with refs/).
+ * @return the log file location.
+ */
+ public File logFor(String name) {
+ if (name.startsWith(R_REFS)) {
+ name = name.substring(R_REFS.length());
+ return new File(logsRefsDir, name);
+ }
+ return new File(logsDir, name);
}
@Override
@@ -202,7 +249,7 @@ public void create() throws IOException {
FileUtils.mkdir(refsDir);
FileUtils.mkdir(new File(refsDir, R_HEADS.substring(R_REFS.length())));
FileUtils.mkdir(new File(refsDir, R_TAGS.substring(R_REFS.length())));
- logWriter.create();
+ newLogWriter(false).create();
}
@Override
@@ -212,7 +259,7 @@ public void close() {
private void clearReferences() {
looseRefs.set(RefList.<LooseRef> emptyList());
- packedRefs.set(PackedRefList.NO_PACKED_REFS);
+ packedRefs.set(NO_PACKED_REFS);
}
@Override
@@ -565,6 +612,16 @@ public RefDirectoryRename newRename(String fromName, String toName)
return new RefDirectoryRename(from, to);
}
+ @Override
+ public PackedBatchRefUpdate newBatchUpdate() {
+ return new PackedBatchRefUpdate(this);
+ }
+
+ @Override
+ public boolean performsAtomicTransactions() {
+ return true;
+ }
+
void stored(RefDirectoryUpdate update, FileSnapshot snapshot) {
final ObjectId target = update.getNewObjectId().copy();
final Ref leaf = update.getRef().getLeaf();
@@ -583,6 +640,9 @@ private void putLooseRef(LooseRef ref) {
void delete(RefDirectoryUpdate update) throws IOException {
Ref dst = update.getRef();
+ if (!update.isDetachingSymbolicRef()) {
+ dst = dst.getLeaf();
+ }
String name = dst.getName();
// Write the packed-refs file using an atomic update. We might
@@ -590,16 +650,20 @@ void delete(RefDirectoryUpdate update) throws IOException {
// we don't miss an edit made externally.
final PackedRefList packed = getPackedRefs();
if (packed.contains(name)) {
- LockFile lck = new LockFile(packedRefsFile);
- if (!lck.lock())
- throw new LockFailedException(packedRefsFile);
+ inProcessPackedRefsLock.lock();
try {
- PackedRefList cur = readPackedRefs();
- int idx = cur.find(name);
- if (0 <= idx)
- commitPackedRefs(lck, cur.remove(idx), packed);
+ LockFile lck = lockPackedRefsOrThrow();
+ try {
+ PackedRefList cur = readPackedRefs();
+ int idx = cur.find(name);
+ if (0 <= idx) {
+ commitPackedRefs(lck, cur.remove(idx), packed, true);
+ }
+ } finally {
+ lck.unlock();
+ }
} finally {
- lck.unlock();
+ inProcessPackedRefsLock.unlock();
}
}
@@ -613,7 +677,7 @@ void delete(RefDirectoryUpdate update) throws IOException {
} while (!looseRefs.compareAndSet(curLoose, newLoose));
int levels = levelsIn(name) - 2;
- delete(logWriter.logFor(name), levels);
+ delete(logFor(name), levels);
if (dst.getStorage().isLoose()) {
update.unlock();
delete(fileFor(name), levels);
@@ -635,75 +699,145 @@ void delete(RefDirectoryUpdate update) throws IOException {
* @throws IOException
*/
public void pack(List<String> refs) throws IOException {
- if (refs.size() == 0)
- return;
+ pack(refs, Collections.emptyMap());
+ }
+
+ PackedRefList pack(Map<String, LockFile> heldLocks) throws IOException {
+ return pack(heldLocks.keySet(), heldLocks);
+ }
+
+ private PackedRefList pack(Collection<String> refs,
+ Map<String, LockFile> heldLocks) throws IOException {
+ for (LockFile ol : heldLocks.values()) {
+ ol.requireLock();
+ }
+ if (refs.size() == 0) {
+ return null;
+ }
FS fs = parent.getFS();
// Lock the packed refs file and read the content
- LockFile lck = new LockFile(packedRefsFile);
- if (!lck.lock())
- throw new IOException(MessageFormat.format(
- JGitText.get().cannotLock, packedRefsFile));
-
+ inProcessPackedRefsLock.lock();
try {
- final PackedRefList packed = getPackedRefs();
- RefList<Ref> cur = readPackedRefs();
+ LockFile lck = lockPackedRefsOrThrow();
+ try {
+ final PackedRefList packed = getPackedRefs();
+ RefList<Ref> cur = readPackedRefs();
- // Iterate over all refs to be packed
- for (String refName : refs) {
- Ref ref = readRef(refName, cur);
- if (ref.isSymbolic())
- continue; // can't pack symbolic refs
- // Add/Update it to packed-refs
- int idx = cur.find(refName);
- if (idx >= 0)
- cur = cur.set(idx, peeledPackedRef(ref));
- else
- cur = cur.add(idx, peeledPackedRef(ref));
- }
-
- // The new content for packed-refs is collected. Persist it.
- commitPackedRefs(lck, cur, packed);
-
- // Now delete the loose refs which are now packed
- for (String refName : refs) {
- // Lock the loose ref
- File refFile = fileFor(refName);
- if (!fs.exists(refFile))
- continue;
- LockFile rLck = new LockFile(refFile);
- if (!rLck.lock())
- continue;
- try {
- LooseRef currentLooseRef = scanRef(null, refName);
- if (currentLooseRef == null || currentLooseRef.isSymbolic())
- continue;
- Ref packedRef = cur.get(refName);
- ObjectId clr_oid = currentLooseRef.getObjectId();
- if (clr_oid != null
- && clr_oid.equals(packedRef.getObjectId())) {
- RefList<LooseRef> curLoose, newLoose;
- do {
- curLoose = looseRefs.get();
- int idx = curLoose.find(refName);
- if (idx < 0)
- break;
- newLoose = curLoose.remove(idx);
- } while (!looseRefs.compareAndSet(curLoose, newLoose));
- int levels = levelsIn(refName) - 2;
- delete(refFile, levels, rLck);
+ // Iterate over all refs to be packed
+ boolean dirty = false;
+ for (String refName : refs) {
+ Ref oldRef = readRef(refName, cur);
+ if (oldRef == null) {
+ continue; // A non-existent ref is already correctly packed.
}
- } finally {
- rLck.unlock();
+ if (oldRef.isSymbolic()) {
+ continue; // can't pack symbolic refs
+ }
+ // Add/Update it to packed-refs
+ Ref newRef = peeledPackedRef(oldRef);
+ if (newRef == oldRef) {
+ // No-op; peeledPackedRef returns the input ref only if it's already
+ // packed, and readRef returns a packed ref only if there is no
+ // loose ref.
+ continue;
+ }
+
+ dirty = true;
+ int idx = cur.find(refName);
+ if (idx >= 0) {
+ cur = cur.set(idx, newRef);
+ } else {
+ cur = cur.add(idx, newRef);
+ }
}
+ if (!dirty) {
+ // All requested refs were already packed accurately
+ return packed;
+ }
+
+ // The new content for packed-refs is collected. Persist it.
+ PackedRefList result = commitPackedRefs(lck, cur, packed,
+ false);
+
+ // Now delete the loose refs which are now packed
+ for (String refName : refs) {
+ // Lock the loose ref
+ File refFile = fileFor(refName);
+ if (!fs.exists(refFile)) {
+ continue;
+ }
+
+ LockFile rLck = heldLocks.get(refName);
+ boolean shouldUnlock;
+ if (rLck == null) {
+ rLck = new LockFile(refFile);
+ if (!rLck.lock()) {
+ continue;
+ }
+ shouldUnlock = true;
+ } else {
+ shouldUnlock = false;
+ }
+
+ try {
+ LooseRef currentLooseRef = scanRef(null, refName);
+ if (currentLooseRef == null || currentLooseRef.isSymbolic()) {
+ continue;
+ }
+ Ref packedRef = cur.get(refName);
+ ObjectId clr_oid = currentLooseRef.getObjectId();
+ if (clr_oid != null
+ && clr_oid.equals(packedRef.getObjectId())) {
+ RefList<LooseRef> curLoose, newLoose;
+ do {
+ curLoose = looseRefs.get();
+ int idx = curLoose.find(refName);
+ if (idx < 0) {
+ break;
+ }
+ newLoose = curLoose.remove(idx);
+ } while (!looseRefs.compareAndSet(curLoose, newLoose));
+ int levels = levelsIn(refName) - 2;
+ delete(refFile, levels, rLck);
+ }
+ } finally {
+ if (shouldUnlock) {
+ rLck.unlock();
+ }
+ }
+ }
+ // Don't fire refsChanged. The refs have not change, only their
+ // storage.
+ return result;
+ } finally {
+ lck.unlock();
}
- // Don't fire refsChanged. The refs have not change, only their
- // storage.
} finally {
- lck.unlock();
+ inProcessPackedRefsLock.unlock();
}
}
+ @Nullable
+ LockFile lockPackedRefs() throws IOException {
+ LockFile lck = new LockFile(packedRefsFile);
+ for (int ms : getRetrySleepMs()) {
+ sleep(ms);
+ if (lck.lock()) {
+ return lck;
+ }
+ }
+ return null;
+ }
+
+ private LockFile lockPackedRefsOrThrow() throws IOException {
+ LockFile lck = lockPackedRefs();
+ if (lck == null) {
+ throw new LockFailedException(packedRefsFile);
+ }
+ return lck;
+ }
+
/**
* Make sure a ref is peeled and has the Storage PACKED. If the given ref
* has this attributes simply return it. Otherwise create a new peeled
@@ -732,9 +866,9 @@ private Ref peeledPackedRef(Ref f)
}
}
- void log(final RefUpdate update, final String msg, final boolean deref)
+ void log(boolean force, RefUpdate update, String msg, boolean deref)
throws IOException {
- logWriter.log(update, msg, deref);
+ newLogWriter(force).log(update, msg, deref);
}
private Ref resolve(final Ref ref, int depth, String prefix,
@@ -769,7 +903,7 @@ else if (0 <= (idx = packed.find(dst.getName())))
return ref;
}
- private PackedRefList getPackedRefs() throws IOException {
+ PackedRefList getPackedRefs() throws IOException {
boolean trustFolderStat = getRepository().getConfig().getBoolean(
ConfigConstants.CONFIG_CORE_SECTION,
ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
@@ -803,7 +937,7 @@ private PackedRefList readPackedRefs() throws IOException {
throw noPackedRefs;
}
// Ignore it and leave the new list empty.
- return PackedRefList.NO_PACKED_REFS;
+ return NO_PACKED_REFS;
}
try {
return new PackedRefList(parsePackedRefs(br), snapshot,
@@ -884,8 +1018,12 @@ private static String copy(final String src, final int off, final int end) {
return new StringBuilder(end - off).append(src, off, end).toString();
}
- private void commitPackedRefs(final LockFile lck, final RefList<Ref> refs,
- final PackedRefList oldPackedList) throws IOException {
+ PackedRefList commitPackedRefs(final LockFile lck, final RefList<Ref> refs,
+ final PackedRefList oldPackedList, boolean changed)
+ throws IOException {
+ // Can't just return packedRefs.get() from this method; it might have been
+ // updated again after writePackedRefs() returns.
+ AtomicReference<PackedRefList> result = new AtomicReference<>();
new RefWriter(refs) {
@Override
protected void writeFile(String name, byte[] content)
@@ -907,10 +1045,31 @@ protected void writeFile(String name, byte[] content)
throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name));
byte[] digest = Constants.newMessageDigest().digest(content);
- packedRefs.compareAndSet(oldPackedList, new PackedRefList(refs,
- lck.getCommitSnapshot(), ObjectId.fromRaw(digest)));
+ PackedRefList newPackedList = new PackedRefList(
+ refs, lck.getCommitSnapshot(), ObjectId.fromRaw(digest));
+
+ // This thread holds the file lock, so no other thread or process should
+ // be able to modify the packed-refs file on disk. If the list changed,
+ // it means something is very wrong, so throw an exception.
+ //
+ // However, we can't use a naive compareAndSet to check whether the
+ // update was successful, because another thread might _read_ the
+ // packed refs file that was written out by this thread while holding
+ // the lock, and update the packedRefs reference to point to that. So
+ // compare the actual contents instead.
+ PackedRefList afterUpdate = packedRefs.updateAndGet(
+ p -> p.id.equals(oldPackedList.id) ? newPackedList : p);
+ if (!afterUpdate.id.equals(newPackedList.id)) {
+ throw new ObjectWritingException(
+ MessageFormat.format(JGitText.get().unableToWrite, name));
+ }
+ if (changed) {
+ modCnt.incrementAndGet();
+ }
+ result.set(newPackedList);
}
}.writePackedRefs();
+ return result.get();
}
private Ref readRef(String name, RefList<Ref> packed) throws IOException {
@@ -1031,8 +1190,31 @@ private static boolean isSymRef(final byte[] buf, int n) {
&& buf[4] == ' ';
}
+ /**
+ * Detect if we are in a clone command execution
+ *
+ * @return {@code true} if we are currently cloning a repository
+ * @throws IOException
+ */
+ boolean isInClone() throws IOException {
+ return hasDanglingHead() && !packedRefsFile.exists() && !hasLooseRef();
+ }
+
+ private boolean hasDanglingHead() throws IOException {
+ Ref head = exactRef(Constants.HEAD);
+ if (head != null) {
+ ObjectId id = head.getObjectId();
+ return id == null || id.equals(ObjectId.zeroId());
+ }
+ return false;
+ }
+
+ private boolean hasLooseRef() throws IOException {
+ return Files.walk(refsDir.toPath()).anyMatch(Files::isRegularFile);
+ }
+
/** If the parent should fire listeners, fires them. */
- private void fireRefsChanged() {
+ void fireRefsChanged() {
final int last = lastNotifiedModCnt.get();
final int curr = modCnt.get();
if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr) && last != 0)
@@ -1107,22 +1289,80 @@ private static void delete(final File file, final int depth, LockFile rLck)
}
}
- private static class PackedRefList extends RefList<Ref> {
- static final PackedRefList NO_PACKED_REFS = new PackedRefList(
- RefList.emptyList(), FileSnapshot.MISSING_FILE,
- ObjectId.zeroId());
+ /**
+ * Get times to sleep while retrying a possibly contentious operation.
+ * <p>
+ * For retrying an operation that might have high contention, such as locking
+ * the {@code packed-refs} file, the caller may implement a retry loop using
+ * the returned values:
+ *
+ * <pre>
+ * for (int toSleepMs : getRetrySleepMs()) {
+ * sleep(toSleepMs);
+ * if (isSuccessful(doSomething())) {
+ * return success;
+ * }
+ * }
+ * return failure;
+ * </pre>
+ *
+ * The first value in the returned iterable is 0, and the caller should treat
+ * a fully-consumed iterator as a timeout.
+ *
+ * @return iterable of times, in milliseconds, that the caller should sleep
+ * before attempting an operation.
+ */
+ Iterable<Integer> getRetrySleepMs() {
+ return retrySleepMs;
+ }
- final FileSnapshot snapshot;
+ void setRetrySleepMs(List<Integer> retrySleepMs) {
+ if (retrySleepMs == null || retrySleepMs.isEmpty()
+ || retrySleepMs.get(0).intValue() != 0) {
+ throw new IllegalArgumentException();
+ }
+ this.retrySleepMs = retrySleepMs;
+ }
- final ObjectId id;
+ /**
+ * Sleep with {@link Thread#sleep(long)}, converting {@link
+ * InterruptedException} to {@link InterruptedIOException}.
+ *
+ * @param ms
+ * time to sleep, in milliseconds; zero or negative is a no-op.
+ * @throws InterruptedIOException
+ * if sleeping was interrupted.
+ */
+ static void sleep(long ms) throws InterruptedIOException {
+ if (ms <= 0) {
+ return;
+ }
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ InterruptedIOException ie = new InterruptedIOException();
+ ie.initCause(e);
+ throw ie;
+ }
+ }
- PackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId i) {
+ static class PackedRefList extends RefList<Ref> {
+
+ private final FileSnapshot snapshot;
+
+ private final ObjectId id;
+
+ private PackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId i) {
super(src);
snapshot = s;
id = i;
}
}
+ private static final PackedRefList NO_PACKED_REFS = new PackedRefList(
+ RefList.emptyList(), FileSnapshot.MISSING_FILE,
+ ObjectId.zeroId());
+
private static LooseSymbolicRef newSymbolicRef(FileSnapshot snapshot,
String name, String target) {
Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java
index 4b803a5..09456c8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java
@@ -188,8 +188,8 @@ protected Result doRename() throws IOException {
}
private boolean renameLog(RefUpdate src, RefUpdate dst) {
- File srcLog = refdb.getLogWriter().logFor(src.getName());
- File dstLog = refdb.getLogWriter().logFor(dst.getName());
+ File srcLog = refdb.logFor(src.getName());
+ File dstLog = refdb.logFor(dst.getName());
if (!srcLog.exists())
return true;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java
index 3c1916b..7ab30fa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java
@@ -50,6 +50,7 @@
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.Repository;
/** Updates any reference stored by {@link RefDirectory}. */
@@ -119,7 +120,7 @@ protected Result doUpdate(final Result status) throws IOException {
msg = strResult;
}
}
- database.log(this, msg, shouldDeref);
+ database.log(isForceRefLog(), this, msg, shouldDeref);
}
if (!lock.commit())
return Result.LOCK_FAILURE;
@@ -127,14 +128,14 @@ protected Result doUpdate(final Result status) throws IOException {
return status;
}
- private String toResultString(final Result status) {
+ private String toResultString(Result status) {
switch (status) {
case FORCED:
- return "forced-update"; //$NON-NLS-1$
+ return ReflogEntry.PREFIX_FORCED_UPDATE;
case FAST_FORWARD:
- return "fast forward"; //$NON-NLS-1$
+ return ReflogEntry.PREFIX_FAST_FORWARD;
case NEW:
- return "created"; //$NON-NLS-1$
+ return ReflogEntry.PREFIX_CREATED;
default:
return null;
}
@@ -158,7 +159,7 @@ protected Result doLink(final String target) throws IOException {
String msg = getRefLogMessage();
if (msg != null)
- database.log(this, msg, false);
+ database.log(isForceRefLog(), this, msg, false);
if (!lock.commit())
return Result.LOCK_FAILURE;
database.storedSymbolicRef(this, lock.getCommitSnapshot(), target);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java
index 16b2a46..8723a8b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java
@@ -139,4 +139,4 @@ public CheckoutEntry parseCheckout() {
else
return null;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java
index 892c1c8..f0bb9c5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java
@@ -46,12 +46,11 @@
package org.eclipse.jgit.internal.storage.file;
import static org.eclipse.jgit.lib.Constants.HEAD;
-import static org.eclipse.jgit.lib.Constants.LOGS;
import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Constants.R_NOTES;
import static org.eclipse.jgit.lib.Constants.R_REFS;
import static org.eclipse.jgit.lib.Constants.R_REMOTES;
-import static org.eclipse.jgit.lib.Constants.R_STASH;
import java.io.File;
import java.io.FileNotFoundException;
@@ -69,110 +68,75 @@
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.ReflogEntry;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
/**
- * Utility for writing reflog entries
+ * Utility for writing reflog entries using the traditional one-file-per-log
+ * format.
*/
public class ReflogWriter {
/**
- * Get the ref name to be used for when locking a ref's log for rewriting
+ * Get the ref name to be used for when locking a ref's log for rewriting.
*
* @param name
* name of the ref, relative to the Git repository top level
* directory (so typically starts with refs/).
- * @return the name of the ref's lock ref
+ * @return the name of the ref's lock ref.
*/
- public static String refLockFor(final String name) {
+ public static String refLockFor(String name) {
return name + LOCK_SUFFIX;
}
- private final Repository parent;
-
- private final File logsDir;
-
- private final File logsRefsDir;
+ private final RefDirectory refdb;
private final boolean forceWrite;
/**
- * Create write for repository
+ * Create writer for ref directory.
*
- * @param repository
+ * @param refdb
*/
- public ReflogWriter(final Repository repository) {
- this(repository, false);
+ public ReflogWriter(RefDirectory refdb) {
+ this(refdb, false);
}
/**
- * Create write for repository
+ * Create writer for ref directory.
*
- * @param repository
+ * @param refdb
* @param forceWrite
* true to write to disk all entries logged, false to respect the
- * repository's config and current log file status
+ * repository's config and current log file status.
*/
- public ReflogWriter(final Repository repository, final boolean forceWrite) {
- final FS fs = repository.getFS();
- parent = repository;
- File gitDir = repository.getDirectory();
- logsDir = fs.resolve(gitDir, LOGS);
- logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS);
+ public ReflogWriter(RefDirectory refdb, boolean forceWrite) {
+ this.refdb = refdb;
this.forceWrite = forceWrite;
}
/**
- * Get repository that reflog is being written for
- *
- * @return file repository
- */
- public Repository getRepository() {
- return parent;
- }
-
- /**
- * Create the log directories
+ * Create the log directories.
*
* @throws IOException
- * @return this writer
+ * @return this writer.
*/
public ReflogWriter create() throws IOException {
- FileUtils.mkdir(logsDir);
- FileUtils.mkdir(logsRefsDir);
- FileUtils.mkdir(new File(logsRefsDir,
- R_HEADS.substring(R_REFS.length())));
+ FileUtils.mkdir(refdb.logsDir);
+ FileUtils.mkdir(refdb.logsRefsDir);
+ FileUtils.mkdir(
+ new File(refdb.logsRefsDir, R_HEADS.substring(R_REFS.length())));
return this;
}
/**
- * Locate the log file on disk for a single reference name.
- *
- * @param name
- * name of the ref, relative to the Git repository top level
- * directory (so typically starts with refs/).
- * @return the log file location.
- */
- public File logFor(String name) {
- if (name.startsWith(R_REFS)) {
- name = name.substring(R_REFS.length());
- return new File(logsRefsDir, name);
- }
- return new File(logsDir, name);
- }
-
- /**
- * Write the given {@link ReflogEntry} entry to the ref's log
+ * Write the given entry to the ref's log.
*
* @param refName
- *
* @param entry
* @return this writer
* @throws IOException
*/
- public ReflogWriter log(final String refName, final ReflogEntry entry)
+ public ReflogWriter log(String refName, ReflogEntry entry)
throws IOException {
return log(refName, entry.getOldId(), entry.getNewId(), entry.getWho(),
entry.getComment());
@@ -189,15 +153,14 @@ public ReflogWriter log(final String refName, final ReflogEntry entry)
* @return this writer
* @throws IOException
*/
- public ReflogWriter log(final String refName, final ObjectId oldId,
- final ObjectId newId, final PersonIdent ident, final String message)
- throws IOException {
+ public ReflogWriter log(String refName, ObjectId oldId,
+ ObjectId newId, PersonIdent ident, String message) throws IOException {
byte[] encoded = encode(oldId, newId, ident, message);
return log(refName, encoded);
}
/**
- * Write the given ref update to the ref's log
+ * Write the given ref update to the ref's log.
*
* @param update
* @param msg
@@ -205,19 +168,19 @@ public ReflogWriter log(final String refName, final ObjectId oldId,
* @return this writer
* @throws IOException
*/
- public ReflogWriter log(final RefUpdate update, final String msg,
- final boolean deref) throws IOException {
- final ObjectId oldId = update.getOldObjectId();
- final ObjectId newId = update.getNewObjectId();
- final Ref ref = update.getRef();
+ public ReflogWriter log(RefUpdate update, String msg,
+ boolean deref) throws IOException {
+ ObjectId oldId = update.getOldObjectId();
+ ObjectId newId = update.getNewObjectId();
+ Ref ref = update.getRef();
PersonIdent ident = update.getRefLogIdent();
if (ident == null)
- ident = new PersonIdent(parent);
+ ident = new PersonIdent(refdb.getRepository());
else
ident = new PersonIdent(ident);
- final byte[] rec = encode(oldId, newId, ident, msg);
+ byte[] rec = encode(oldId, newId, ident, msg);
if (deref && ref.isSymbolic()) {
log(ref.getName(), rec);
log(ref.getLeaf().getName(), rec);
@@ -229,33 +192,34 @@ public ReflogWriter log(final RefUpdate update, final String msg,
private byte[] encode(ObjectId oldId, ObjectId newId, PersonIdent ident,
String message) {
- final StringBuilder r = new StringBuilder();
+ StringBuilder r = new StringBuilder();
r.append(ObjectId.toString(oldId));
r.append(' ');
r.append(ObjectId.toString(newId));
r.append(' ');
r.append(ident.toExternalString());
r.append('\t');
- r.append(message.replace("\r\n", " ").replace("\n", " ")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ r.append(
+ message.replace("\r\n", " ") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("\n", " ")); //$NON-NLS-1$ //$NON-NLS-2$
r.append('\n');
return Constants.encode(r.toString());
}
- private ReflogWriter log(final String refName, final byte[] rec)
- throws IOException {
- final File log = logFor(refName);
- final boolean write = forceWrite
+ private ReflogWriter log(String refName, byte[] rec) throws IOException {
+ File log = refdb.logFor(refName);
+ boolean write = forceWrite
|| (isLogAllRefUpdates() && shouldAutoCreateLog(refName))
|| log.isFile();
if (!write)
return this;
- WriteConfig wc = getRepository().getConfig().get(WriteConfig.KEY);
+ WriteConfig wc = refdb.getRepository().getConfig().get(WriteConfig.KEY);
FileOutputStream out;
try {
out = new FileOutputStream(log, true);
} catch (FileNotFoundException err) {
- final File dir = log.getParentFile();
+ File dir = log.getParentFile();
if (dir.exists())
throw err;
if (!dir.mkdirs() && !dir.isDirectory())
@@ -279,13 +243,14 @@ private ReflogWriter log(final String refName, final byte[] rec)
}
private boolean isLogAllRefUpdates() {
- return parent.getConfig().get(CoreConfig.KEY).isLogAllRefUpdates();
+ return refdb.getRepository().getConfig().get(CoreConfig.KEY)
+ .isLogAllRefUpdates();
}
- private boolean shouldAutoCreateLog(final String refName) {
- return refName.equals(HEAD) //
- || refName.startsWith(R_HEADS) //
- || refName.startsWith(R_REMOTES) //
- || refName.equals(R_STASH);
+ private boolean shouldAutoCreateLog(String refName) {
+ return refName.equals(HEAD)
+ || refName.startsWith(R_HEADS)
+ || refName.startsWith(R_REMOTES)
+ || refName.startsWith(R_NOTES);
}
-}
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java
index 373a494..5fe0429 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java
@@ -136,4 +136,4 @@ public void writeChars(String s) throws IOException {
public void writeUTF(String s) throws IOException {
throw new UnsupportedOperationException();
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
index a525c85..6196006 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
@@ -496,31 +496,16 @@ private void removeAll(final PackFile pack) {
private void gc() {
Ref r;
while ((r = (Ref) queue.poll()) != null) {
- // Sun's Java 5 and 6 implementation have a bug where a Reference
- // can be enqueued and dequeued twice on the same reference queue
- // due to a race condition within ReferenceQueue.enqueue(Reference).
- //
- // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6837858
- //
- // We CANNOT permit a Reference to come through us twice, as it will
- // skew the resource counters we maintain. Our canClear() check here
- // provides a way to skip the redundant dequeues, if any.
- //
- if (r.canClear()) {
- clear(r);
+ clear(r);
- boolean found = false;
- final int s = slot(r.pack, r.position);
- final Entry e1 = table.get(s);
- for (Entry n = e1; n != null; n = n.next) {
- if (n.ref == r) {
- n.dead = true;
- found = true;
- break;
- }
- }
- if (found)
+ final int s = slot(r.pack, r.position);
+ final Entry e1 = table.get(s);
+ for (Entry n = e1; n != null; n = n.next) {
+ if (n.ref == r) {
+ n.dead = true;
table.compareAndSet(s, e1, clean(e1));
+ break;
+ }
}
}
}
@@ -581,8 +566,6 @@ private static class Ref extends SoftReference<ByteWindow> {
long lastAccess;
- private boolean cleared;
-
protected Ref(final PackFile pack, final long position,
final ByteWindow v, final ReferenceQueue<ByteWindow> queue) {
super(v, queue);
@@ -590,13 +573,6 @@ protected Ref(final PackFile pack, final long position,
this.position = position;
this.size = v.size();
}
-
- final synchronized boolean canClear() {
- if (cleared)
- return false;
- cleared = true;
- return true;
- }
}
private static final class Lock {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WriteConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WriteConfig.java
index 1e2b239..d9cbbd8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WriteConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WriteConfig.java
@@ -49,12 +49,7 @@
class WriteConfig {
/** Key for {@link Config#get(SectionParser)}. */
- static final Config.SectionParser<WriteConfig> KEY = new SectionParser<WriteConfig>() {
- @Override
- public WriteConfig parse(final Config cfg) {
- return new WriteConfig(cfg);
- }
- };
+ static final Config.SectionParser<WriteConfig> KEY = WriteConfig::new;
private final int compression;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/BlockSource.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/BlockSource.java
new file mode 100644
index 0000000..0a5f9c1
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/BlockSource.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.io;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+/**
+ * Provides content blocks of file.
+ * <p>
+ * {@code BlockSource} implementations must decide if they will be thread-safe,
+ * or not.
+ */
+public abstract class BlockSource implements AutoCloseable {
+ /**
+ * Wrap a byte array as a {@code BlockSource}.
+ *
+ * @param content
+ * input file.
+ * @return block source to read from {@code content}.
+ */
+ public static BlockSource from(byte[] content) {
+ return new BlockSource() {
+ @Override
+ public ByteBuffer read(long pos, int cnt) {
+ ByteBuffer buf = ByteBuffer.allocate(cnt);
+ if (pos < content.length) {
+ int p = (int) pos;
+ int n = Math.min(cnt, content.length - p);
+ buf.put(content, p, n);
+ }
+ return buf;
+ }
+
+ @Override
+ public long size() {
+ return content.length;
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+ };
+ }
+
+ /**
+ * Read from a {@code FileInputStream}.
+ * <p>
+ * The returned {@code BlockSource} is not thread-safe, as it must seek the
+ * file channel to read a block.
+ *
+ * @param in
+ * the file. The {@code BlockSource} will close {@code in}.
+ * @return wrapper for {@code in}.
+ */
+ public static BlockSource from(FileInputStream in) {
+ return from(in.getChannel());
+ }
+
+ /**
+ * Read from a {@code FileChannel}.
+ * <p>
+ * The returned {@code BlockSource} is not thread-safe, as it must seek the
+ * file channel to read a block.
+ *
+ * @param ch
+ * the file. The {@code BlockSource} will close {@code ch}.
+ * @return wrapper for {@code ch}.
+ */
+ public static BlockSource from(FileChannel ch) {
+ return new BlockSource() {
+ @Override
+ public ByteBuffer read(long pos, int blockSize) throws IOException {
+ ByteBuffer b = ByteBuffer.allocate(blockSize);
+ ch.position(pos);
+ int n;
+ do {
+ n = ch.read(b);
+ } while (n > 0 && b.position() < blockSize);
+ return b;
+ }
+
+ @Override
+ public long size() throws IOException {
+ return ch.size();
+ }
+
+ @Override
+ public void close() {
+ try {
+ ch.close();
+ } catch (IOException e) {
+ // Ignore close failures of read-only files.
+ }
+ }
+ };
+ }
+
+ /**
+ * Read a block from the file.
+ * <p>
+ * To reduce copying, the returned ByteBuffer should have an accessible
+ * array and {@code arrayOffset() == 0}. The caller will discard the
+ * ByteBuffer and directly use the backing array.
+ *
+ * @param position
+ * position of the block in the file, specified in bytes from the
+ * beginning of the file.
+ * @param blockSize
+ * size to read.
+ * @return buffer containing the block content.
+ * @throws IOException
+ * if block cannot be read.
+ */
+ public abstract ByteBuffer read(long position, int blockSize)
+ throws IOException;
+
+ /**
+ * Determine the size of the file.
+ *
+ * @return total number of bytes in the file.
+ * @throws IOException
+ * if size cannot be obtained.
+ */
+ public abstract long size() throws IOException;
+
+ /**
+ * Advise the {@code BlockSource} a sequential scan is starting.
+ *
+ * @param startPos
+ * starting position.
+ * @param endPos
+ * ending position.
+ */
+ public void adviseSequentialRead(long startPos, long endPos) {
+ // Do nothing by default.
+ }
+
+ @Override
+ public abstract void close();
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndexScanner.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndexScanner.java
index 7e10878..969d02b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndexScanner.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndexScanner.java
@@ -127,4 +127,4 @@ private static int tableSize(final int worstCaseBlockCnt) {
sz <<= 1;
return sz;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectToPack.java
index a089657..bc7a603 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectToPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectToPack.java
@@ -182,6 +182,7 @@ public final boolean isWritten() {
}
/** @return the type of this object. */
+ @Override
public final int getType() {
return (flags >> TYPE_SHIFT) & 0x7;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java
index 248692f..e8bbf78 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java
@@ -59,6 +59,9 @@ public class PackExt {
/** A pack bitmap index file extension. */
public static final PackExt BITMAP_INDEX = newPackExt("bitmap"); //$NON-NLS-1$
+ /** A reftable file. */
+ public static final PackExt REFTABLE = newPackExt("ref"); //$NON-NLS-1$
+
/** @return all of the PackExt values. */
public static PackExt[] values() {
return VALUES;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java
new file mode 100644
index 0000000..ce2ba4a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java
@@ -0,0 +1,589 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.reftable;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.internal.storage.reftable.BlockWriter.compare;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_DATA;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_NONE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_1ID;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_2ID;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_NONE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_SYMREF;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_TYPE_MASK;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.reverseUpdateIndex;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.io.BlockSource;
+import org.eclipse.jgit.lib.CheckoutEntry;
+import org.eclipse.jgit.lib.InflaterCache;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.util.LongList;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/** Reads a single block for {@link ReftableReader}. */
+class BlockReader {
+ private byte blockType;
+ private long endPosition;
+ private boolean truncated;
+
+ private byte[] buf;
+ private int bufLen;
+ private int ptr;
+
+ private int keysStart;
+ private int keysEnd;
+
+ private int restartCnt;
+ private int restartTbl;
+
+ private byte[] nameBuf = new byte[256];
+ private int nameLen;
+ private int valueType;
+
+ byte type() {
+ return blockType;
+ }
+
+ boolean truncated() {
+ return truncated;
+ }
+
+ long endPosition() {
+ return endPosition;
+ }
+
+ boolean next() {
+ return ptr < keysEnd;
+ }
+
+ void parseKey() {
+ int pfx = readVarint32();
+ valueType = readVarint32();
+ int sfx = valueType >>> 3;
+ if (pfx + sfx > nameBuf.length) {
+ int n = Math.max(pfx + sfx, nameBuf.length * 2);
+ nameBuf = Arrays.copyOf(nameBuf, n);
+ }
+ System.arraycopy(buf, ptr, nameBuf, pfx, sfx);
+ ptr += sfx;
+ nameLen = pfx + sfx;
+ }
+
+ String name() {
+ int len = nameLen;
+ if (blockType == LOG_BLOCK_TYPE) {
+ len -= 9;
+ }
+ return RawParseUtils.decode(UTF_8, nameBuf, 0, len);
+ }
+
+ boolean match(byte[] match, boolean matchIsPrefix) {
+ int len = nameLen;
+ if (blockType == LOG_BLOCK_TYPE) {
+ len -= 9;
+ }
+ if (matchIsPrefix) {
+ return len >= match.length
+ && compare(
+ match, 0, match.length,
+ nameBuf, 0, match.length) == 0;
+ }
+ return compare(match, 0, match.length, nameBuf, 0, len) == 0;
+ }
+
+ long readPositionFromIndex() throws IOException {
+ if (blockType != INDEX_BLOCK_TYPE) {
+ throw invalidBlock();
+ }
+
+ readVarint32(); // skip prefix length
+ int n = readVarint32() >>> 3;
+ ptr += n; // skip name
+ return readVarint64();
+ }
+
+ long readUpdateIndexDelta() {
+ return readVarint64();
+ }
+
+ Ref readRef() throws IOException {
+ String name = RawParseUtils.decode(UTF_8, nameBuf, 0, nameLen);
+ switch (valueType & VALUE_TYPE_MASK) {
+ case VALUE_NONE: // delete
+ return newRef(name);
+
+ case VALUE_1ID:
+ return new ObjectIdRef.PeeledNonTag(PACKED, name, readValueId());
+
+ case VALUE_2ID: { // annotated tag
+ ObjectId id1 = readValueId();
+ ObjectId id2 = readValueId();
+ return new ObjectIdRef.PeeledTag(PACKED, name, id1, id2);
+ }
+
+ case VALUE_SYMREF: {
+ String val = readValueString();
+ return new SymbolicRef(name, newRef(val));
+ }
+
+ default:
+ throw invalidBlock();
+ }
+ }
+
+ @Nullable
+ LongList readBlockPositionList() {
+ int n = valueType & VALUE_TYPE_MASK;
+ if (n == 0) {
+ n = readVarint32();
+ if (n == 0) {
+ return null;
+ }
+ }
+
+ LongList b = new LongList(n);
+ b.add(readVarint64());
+ for (int j = 1; j < n; j++) {
+ long prior = b.get(j - 1);
+ b.add(prior + readVarint64());
+ }
+ return b;
+ }
+
+ long readLogUpdateIndex() {
+ return reverseUpdateIndex(NB.decodeUInt64(nameBuf, nameLen - 8));
+ }
+
+ @Nullable
+ ReflogEntry readLogEntry() {
+ if ((valueType & VALUE_TYPE_MASK) == LOG_NONE) {
+ return null;
+ }
+
+ ObjectId oldId = readValueId();
+ ObjectId newId = readValueId();
+ PersonIdent who = readPersonIdent();
+ String msg = readValueString();
+
+ return new ReflogEntry() {
+ @Override
+ public ObjectId getOldId() {
+ return oldId;
+ }
+
+ @Override
+ public ObjectId getNewId() {
+ return newId;
+ }
+
+ @Override
+ public PersonIdent getWho() {
+ return who;
+ }
+
+ @Override
+ public String getComment() {
+ return msg;
+ }
+
+ @Override
+ public CheckoutEntry parseCheckout() {
+ return null;
+ }
+ };
+ }
+
+ private ObjectId readValueId() {
+ ObjectId id = ObjectId.fromRaw(buf, ptr);
+ ptr += OBJECT_ID_LENGTH;
+ return id;
+ }
+
+ private String readValueString() {
+ int len = readVarint32();
+ int end = ptr + len;
+ String s = RawParseUtils.decode(UTF_8, buf, ptr, end);
+ ptr = end;
+ return s;
+ }
+
+ private PersonIdent readPersonIdent() {
+ String name = readValueString();
+ String email = readValueString();
+ long ms = readVarint64() * 1000;
+ int tz = readInt16();
+ return new PersonIdent(name, email, ms, tz);
+ }
+
+ void readBlock(BlockSource src, long pos, int fileBlockSize)
+ throws IOException {
+ readBlockIntoBuf(src, pos, fileBlockSize);
+ parseBlockStart(src, pos, fileBlockSize);
+ }
+
+ private void readBlockIntoBuf(BlockSource src, long pos, int size)
+ throws IOException {
+ ByteBuffer b = src.read(pos, size);
+ bufLen = b.position();
+ if (bufLen <= 0) {
+ throw invalidBlock();
+ }
+ if (b.hasArray() && b.arrayOffset() == 0) {
+ buf = b.array();
+ } else {
+ buf = new byte[bufLen];
+ b.flip();
+ b.get(buf);
+ }
+ endPosition = pos + bufLen;
+ }
+
+ private void parseBlockStart(BlockSource src, long pos, int fileBlockSize)
+ throws IOException {
+ ptr = 0;
+ if (pos == 0) {
+ if (bufLen == FILE_HEADER_LEN) {
+ setupEmptyFileBlock();
+ return;
+ }
+ ptr += FILE_HEADER_LEN; // first block begins with file header
+ }
+
+ int typeAndSize = NB.decodeInt32(buf, ptr);
+ ptr += 4;
+
+ blockType = (byte) (typeAndSize >>> 24);
+ int blockLen = decodeBlockLen(typeAndSize);
+ if (blockType == LOG_BLOCK_TYPE) {
+ // Log blocks must be inflated after the header.
+ long deflatedSize = inflateBuf(src, pos, blockLen, fileBlockSize);
+ endPosition = pos + 4 + deflatedSize;
+ }
+ if (bufLen < blockLen) {
+ if (blockType != INDEX_BLOCK_TYPE) {
+ throw invalidBlock();
+ }
+ // Its OK during sequential scan for an index block to have been
+ // partially read and be truncated in-memory. This happens when
+ // the index block is larger than the file's blockSize. Caller
+ // will break out of its scan loop once it sees the blockType.
+ truncated = true;
+ } else if (bufLen > blockLen) {
+ bufLen = blockLen;
+ }
+
+ if (blockType != FILE_BLOCK_TYPE) {
+ restartCnt = NB.decodeUInt16(buf, bufLen - 2);
+ restartTbl = bufLen - (restartCnt * 3 + 2);
+ keysStart = ptr;
+ keysEnd = restartTbl;
+ } else {
+ keysStart = ptr;
+ keysEnd = ptr;
+ }
+ }
+
+ static int decodeBlockLen(int typeAndSize) {
+ return typeAndSize & 0xffffff;
+ }
+
+ private long inflateBuf(BlockSource src, long pos, int blockLen,
+ int fileBlockSize) throws IOException {
+ byte[] dst = new byte[blockLen];
+ System.arraycopy(buf, 0, dst, 0, 4);
+
+ long deflatedSize = 0;
+ Inflater inf = InflaterCache.get();
+ try {
+ inf.setInput(buf, ptr, bufLen - ptr);
+ for (int o = 4;;) {
+ int n = inf.inflate(dst, o, dst.length - o);
+ o += n;
+ if (inf.finished()) {
+ deflatedSize = inf.getBytesRead();
+ break;
+ } else if (n <= 0 && inf.needsInput()) {
+ long p = pos + 4 + inf.getBytesRead();
+ readBlockIntoBuf(src, p, fileBlockSize);
+ inf.setInput(buf, 0, bufLen);
+ } else if (n <= 0) {
+ throw invalidBlock();
+ }
+ }
+ } catch (DataFormatException e) {
+ throw invalidBlock(e);
+ } finally {
+ InflaterCache.release(inf);
+ }
+
+ buf = dst;
+ bufLen = dst.length;
+ return deflatedSize;
+ }
+
+ private void setupEmptyFileBlock() {
+ // An empty reftable has only the file header in first block.
+ blockType = FILE_BLOCK_TYPE;
+ ptr = FILE_HEADER_LEN;
+ restartCnt = 0;
+ restartTbl = bufLen;
+ keysStart = bufLen;
+ keysEnd = bufLen;
+ }
+
+ void verifyIndex() throws IOException {
+ if (blockType != INDEX_BLOCK_TYPE || truncated) {
+ throw invalidBlock();
+ }
+ }
+
+ /**
+ * Finds a key in the block and positions the current pointer on its record.
+ * <p>
+ * As a side-effect this method arranges for the current pointer to be near
+ * or exactly on {@code key}, allowing other methods to access data from
+ * that current record:
+ * <ul>
+ * <li>{@link #name()}
+ * <li>{@link #match(byte[], boolean)}
+ * <li>{@link #readRef()}
+ * <li>{@link #readLogUpdateIndex()}
+ * <li>{@link #readLogEntry()}
+ * <li>{@link #readBlockPositionList()}
+ * </ul>
+ *
+ * @param key
+ * key to find.
+ * @return {@code <0} if the key occurs before the start of this block;
+ * {@code 0} if the block is positioned on the key; {@code >0} if
+ * the key occurs after the last key of this block.
+ */
+ int seekKey(byte[] key) {
+ int low = 0;
+ int end = restartCnt;
+ for (;;) {
+ int mid = (low + end) >>> 1;
+ int p = NB.decodeUInt24(buf, restartTbl + mid * 3);
+ ptr = p + 1; // skip 0 prefix length
+ int n = readVarint32() >>> 3;
+ int cmp = compare(key, 0, key.length, buf, ptr, n);
+ if (cmp < 0) {
+ end = mid;
+ } else if (cmp == 0) {
+ ptr = p;
+ return 0;
+ } else /* if (cmp > 0) */ {
+ low = mid + 1;
+ }
+ if (low >= end) {
+ return scanToKey(key, p, low, cmp);
+ }
+ }
+ }
+
+ /**
+ * Performs the linear search step within a restart interval.
+ * <p>
+ * Starts at a restart position whose key sorts before (or equal to)
+ * {@code key} and walks sequentially through the following prefix
+ * compressed records to find {@code key}.
+ *
+ * @param key
+ * key the caller wants to find.
+ * @param rPtr
+ * current record pointer from restart table binary search.
+ * @param rIdx
+ * current restart table index.
+ * @param rCmp
+ * result of compare from restart table binary search.
+ * @return {@code <0} if the key occurs before the start of this block;
+ * {@code 0} if the block is positioned on the key; {@code >0} if
+ * the key occurs after the last key of this block.
+ */
+ private int scanToKey(byte[] key, int rPtr, int rIdx, int rCmp) {
+ if (rCmp < 0) {
+ if (rIdx == 0) {
+ ptr = keysStart;
+ return -1;
+ }
+ ptr = NB.decodeUInt24(buf, restartTbl + (rIdx - 1) * 3);
+ } else {
+ ptr = rPtr;
+ }
+
+ int cmp;
+ do {
+ int savePtr = ptr;
+ parseKey();
+ cmp = compare(key, 0, key.length, nameBuf, 0, nameLen);
+ if (cmp <= 0) {
+ // cmp < 0, name should be in this block, but is not.
+ // cmp = 0, block is positioned at name.
+ ptr = savePtr;
+ return cmp < 0 && savePtr == keysStart ? -1 : 0;
+ }
+ skipValue();
+ } while (ptr < keysEnd);
+ return cmp;
+ }
+
+ void skipValue() {
+ switch (blockType) {
+ case REF_BLOCK_TYPE:
+ readVarint64(); // update_index_delta
+ switch (valueType & VALUE_TYPE_MASK) {
+ case VALUE_NONE:
+ return;
+ case VALUE_1ID:
+ ptr += OBJECT_ID_LENGTH;
+ return;
+ case VALUE_2ID:
+ ptr += 2 * OBJECT_ID_LENGTH;
+ return;
+ case VALUE_SYMREF:
+ skipString();
+ return;
+ }
+ break;
+
+ case OBJ_BLOCK_TYPE: {
+ int n = valueType & VALUE_TYPE_MASK;
+ if (n == 0) {
+ n = readVarint32();
+ }
+ while (n-- > 0) {
+ readVarint32();
+ }
+ return;
+ }
+
+ case INDEX_BLOCK_TYPE:
+ readVarint32();
+ return;
+
+ case LOG_BLOCK_TYPE:
+ if ((valueType & VALUE_TYPE_MASK) == LOG_NONE) {
+ return;
+ } else if ((valueType & VALUE_TYPE_MASK) == LOG_DATA) {
+ ptr += 2 * OBJECT_ID_LENGTH; // oldId, newId
+ skipString(); // name
+ skipString(); // email
+ readVarint64(); // time
+ ptr += 2; // tz
+ skipString(); // msg
+ return;
+ }
+ }
+
+ throw new IllegalStateException();
+ }
+
+ private void skipString() {
+ int n = readVarint32(); // string length
+ ptr += n;
+ }
+
+ private short readInt16() {
+ return (short) NB.decodeUInt16(buf, ptr += 2);
+ }
+
+ private int readVarint32() {
+ byte c = buf[ptr++];
+ int val = c & 0x7f;
+ while ((c & 0x80) != 0) {
+ c = buf[ptr++];
+ val++;
+ val <<= 7;
+ val |= (c & 0x7f);
+ }
+ return val;
+ }
+
+ private long readVarint64() {
+ byte c = buf[ptr++];
+ long val = c & 0x7f;
+ while ((c & 0x80) != 0) {
+ c = buf[ptr++];
+ val++;
+ val <<= 7;
+ val |= (c & 0x7f);
+ }
+ return val;
+ }
+
+ private static Ref newRef(String name) {
+ return new ObjectIdRef.Unpeeled(NEW, name, null);
+ }
+
+ private static IOException invalidBlock() {
+ return invalidBlock(null);
+ }
+
+ private static IOException invalidBlock(Throwable cause) {
+ return new IOException(JGitText.get().invalidReftableBlock, cause);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockSizeTooSmallException.java
similarity index 78%
rename from org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockSizeTooSmallException.java
index 98a2a94..cb0f988 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockSizeTooSmallException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011, Google Inc.
+ * Copyright (C) 2017, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -41,20 +41,22 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.eclipse.jgit.internal.storage.dfs;
+package org.eclipse.jgit.internal.storage.reftable;
-import java.util.concurrent.atomic.AtomicLong;
+import java.io.IOException;
-final class DfsPackKey {
- final int hash;
+/** Thrown if {@link ReftableWriter} cannot fit a reference. */
+public class BlockSizeTooSmallException extends IOException {
+ private static final long serialVersionUID = 1L;
- final AtomicLong cachedSize;
+ private final int minBlockSize;
- DfsPackKey() {
- // Multiply by 31 here so we can more directly combine with another
- // value without doing the multiply there.
- //
- hash = System.identityHashCode(this) * 31;
- cachedSize = new AtomicLong();
+ BlockSizeTooSmallException(int b) {
+ minBlockSize = b;
+ }
+
+ /** @return minimum block size in bytes reftable requires to write a ref. */
+ public int getMinimumBlockSize() {
+ return minBlockSize;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java
new file mode 100644
index 0000000..b3173e8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java
@@ -0,0 +1,605 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.reftable;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_DATA;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_NONE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_RESTARTS;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_1ID;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_2ID;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_NONE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_SYMREF;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VALUE_TYPE_MASK;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.reverseUpdateIndex;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableOutputStream.computeVarintSize;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.util.IntList;
+import org.eclipse.jgit.util.LongList;
+import org.eclipse.jgit.util.NB;
+
+/** Formats and writes blocks for {@link ReftableWriter}. */
+class BlockWriter {
+ private final byte blockType;
+ private final byte keyType;
+ private final List<Entry> entries;
+ private final int blockLimitBytes;
+ private final int restartInterval;
+
+ private int entriesSumBytes;
+ private int restartCnt;
+
+ BlockWriter(byte type, byte kt, int bs, int ri) {
+ blockType = type;
+ keyType = kt;
+ blockLimitBytes = bs;
+ restartInterval = ri;
+ entries = new ArrayList<>(estimateEntryCount(type, kt, bs));
+ }
+
+ private static int estimateEntryCount(byte blockType, byte keyType,
+ int blockLimitBytes) {
+ double avgBytesPerEntry;
+ switch (blockType) {
+ case REF_BLOCK_TYPE:
+ default:
+ avgBytesPerEntry = 35.31;
+ break;
+
+ case OBJ_BLOCK_TYPE:
+ avgBytesPerEntry = 4.19;
+ break;
+
+ case LOG_BLOCK_TYPE:
+ avgBytesPerEntry = 101.14;
+ break;
+
+ case INDEX_BLOCK_TYPE:
+ switch (keyType) {
+ case REF_BLOCK_TYPE:
+ case LOG_BLOCK_TYPE:
+ default:
+ avgBytesPerEntry = 27.44;
+ break;
+
+ case OBJ_BLOCK_TYPE:
+ avgBytesPerEntry = 11.57;
+ break;
+ }
+ }
+
+ int cnt = (int) (Math.ceil(blockLimitBytes / avgBytesPerEntry));
+ return Math.min(cnt, 4096);
+ }
+
+ byte blockType() {
+ return blockType;
+ }
+
+ boolean padBetweenBlocks() {
+ return padBetweenBlocks(blockType)
+ || (blockType == INDEX_BLOCK_TYPE && padBetweenBlocks(keyType));
+ }
+
+ static boolean padBetweenBlocks(byte type) {
+ return type == REF_BLOCK_TYPE || type == OBJ_BLOCK_TYPE;
+ }
+
+ byte[] lastKey() {
+ return entries.get(entries.size() - 1).key;
+ }
+
+ int currentSize() {
+ return computeBlockBytes(0, false);
+ }
+
+ void mustAdd(Entry entry) throws BlockSizeTooSmallException {
+ if (!tryAdd(entry, true)) {
+ // Insanely long names need a larger block size.
+ throw blockSizeTooSmall(entry);
+ }
+ }
+
+ boolean tryAdd(Entry entry) {
+ if (entry instanceof ObjEntry
+ && computeBlockBytes(entry.sizeBytes(), 1) > blockLimitBytes) {
+ // If the ObjEntry has so many ref block pointers that its
+ // encoding overflows any block, reconfigure it to tell readers to
+ // instead scan all refs for this ObjectId. That significantly
+ // shrinks the entry to a very small size, which may now fit into
+ // this block.
+ ((ObjEntry) entry).markScanRequired();
+ }
+
+ if (tryAdd(entry, true)) {
+ return true;
+ } else if (nextShouldBeRestart()) {
+ // It was time for another restart, but the entry doesn't fit
+ // with its complete key, as the block is nearly full. Try to
+ // force it to fit with prefix compression rather than waste
+ // the tail of the block with padding.
+ return tryAdd(entry, false);
+ }
+ return false;
+ }
+
+ private boolean tryAdd(Entry entry, boolean tryRestart) {
+ byte[] key = entry.key;
+ int prefixLen = 0;
+ boolean restart = tryRestart && nextShouldBeRestart();
+ if (!restart) {
+ Entry priorEntry = entries.get(entries.size() - 1);
+ byte[] prior = priorEntry.key;
+ prefixLen = commonPrefix(prior, prior.length, key);
+ if (prefixLen <= 5 /* "refs/" */ && keyType == REF_BLOCK_TYPE) {
+ // Force restart points at transitions between namespaces
+ // such as "refs/heads/" to "refs/tags/".
+ restart = true;
+ prefixLen = 0;
+ } else if (prefixLen == 0) {
+ restart = true;
+ }
+ }
+
+ entry.restart = restart;
+ entry.prefixLen = prefixLen;
+ int entryBytes = entry.sizeBytes();
+ if (computeBlockBytes(entryBytes, restart) > blockLimitBytes) {
+ return false;
+ }
+
+ entriesSumBytes += entryBytes;
+ entries.add(entry);
+ if (restart) {
+ restartCnt++;
+ }
+ return true;
+ }
+
+ private boolean nextShouldBeRestart() {
+ int cnt = entries.size();
+ return (cnt == 0 || ((cnt + 1) % restartInterval) == 0)
+ && restartCnt < MAX_RESTARTS;
+ }
+
+ private int computeBlockBytes(int entryBytes, boolean restart) {
+ return computeBlockBytes(
+ entriesSumBytes + entryBytes,
+ restartCnt + (restart ? 1 : 0));
+ }
+
+ private static int computeBlockBytes(int entryBytes, int restartCnt) {
+ return 4 // 4-byte block header
+ + entryBytes
+ + restartCnt * 3 // restart_offset
+ + 2; // 2-byte restart_count
+ }
+
+ void writeTo(ReftableOutputStream os) throws IOException {
+ os.beginBlock(blockType);
+ IntList restarts = new IntList(restartCnt);
+ for (Entry entry : entries) {
+ if (entry.restart) {
+ restarts.add(os.bytesWrittenInBlock());
+ }
+ entry.writeKey(os);
+ entry.writeValue(os);
+ }
+ if (restarts.size() == 0 || restarts.size() > MAX_RESTARTS) {
+ throw new IllegalStateException();
+ }
+ for (int i = 0; i < restarts.size(); i++) {
+ os.writeInt24(restarts.get(i));
+ }
+ os.writeInt16(restarts.size());
+ os.flushBlock();
+ }
+
+ private BlockSizeTooSmallException blockSizeTooSmall(Entry entry) {
+ // Compute size required to fit this entry by itself.
+ int min = FILE_HEADER_LEN + computeBlockBytes(entry.sizeBytes(), 1);
+ return new BlockSizeTooSmallException(min);
+ }
+
+ static int commonPrefix(byte[] a, int n, byte[] b) {
+ int len = Math.min(n, Math.min(a.length, b.length));
+ for (int i = 0; i < len; i++) {
+ if (a[i] != b[i]) {
+ return i;
+ }
+ }
+ return len;
+ }
+
+ static int encodeSuffixAndType(int sfx, int valueType) {
+ return (sfx << 3) | valueType;
+ }
+
+ static int compare(
+ byte[] a, int ai, int aLen,
+ byte[] b, int bi, int bLen) {
+ int aEnd = ai + aLen;
+ int bEnd = bi + bLen;
+ while (ai < aEnd && bi < bEnd) {
+ int c = (a[ai++] & 0xff) - (b[bi++] & 0xff);
+ if (c != 0) {
+ return c;
+ }
+ }
+ return aLen - bLen;
+ }
+
+ static abstract class Entry {
+ static int compare(Entry ea, Entry eb) {
+ byte[] a = ea.key;
+ byte[] b = eb.key;
+ return BlockWriter.compare(a, 0, a.length, b, 0, b.length);
+ }
+
+ final byte[] key;
+ int prefixLen;
+ boolean restart;
+
+ Entry(byte[] key) {
+ this.key = key;
+ }
+
+ void writeKey(ReftableOutputStream os) {
+ int sfxLen = key.length - prefixLen;
+ os.writeVarint(prefixLen);
+ os.writeVarint(encodeSuffixAndType(sfxLen, valueType()));
+ os.write(key, prefixLen, sfxLen);
+ }
+
+ int sizeBytes() {
+ int sfxLen = key.length - prefixLen;
+ int sfx = encodeSuffixAndType(sfxLen, valueType());
+ return computeVarintSize(prefixLen)
+ + computeVarintSize(sfx)
+ + sfxLen
+ + valueSize();
+ }
+
+ abstract byte blockType();
+ abstract int valueType();
+ abstract int valueSize();
+ abstract void writeValue(ReftableOutputStream os) throws IOException;
+ }
+
+ static class IndexEntry extends Entry {
+ private final long blockPosition;
+
+ IndexEntry(byte[] key, long blockPosition) {
+ super(key);
+ this.blockPosition = blockPosition;
+ }
+
+ @Override
+ byte blockType() {
+ return INDEX_BLOCK_TYPE;
+ }
+
+ @Override
+ int valueType() {
+ return 0;
+ }
+
+ @Override
+ int valueSize() {
+ return computeVarintSize(blockPosition);
+ }
+
+ @Override
+ void writeValue(ReftableOutputStream os) {
+ os.writeVarint(blockPosition);
+ }
+ }
+
+ static class RefEntry extends Entry {
+ final Ref ref;
+ final long updateIndexDelta;
+
+ RefEntry(Ref ref, long updateIndexDelta) {
+ super(nameUtf8(ref));
+ this.ref = ref;
+ this.updateIndexDelta = updateIndexDelta;
+ }
+
+ @Override
+ byte blockType() {
+ return REF_BLOCK_TYPE;
+ }
+
+ @Override
+ int valueType() {
+ if (ref.isSymbolic()) {
+ return VALUE_SYMREF;
+ } else if (ref.getStorage() == NEW && ref.getObjectId() == null) {
+ return VALUE_NONE;
+ } else if (ref.getPeeledObjectId() != null) {
+ return VALUE_2ID;
+ } else {
+ return VALUE_1ID;
+ }
+ }
+
+ @Override
+ int valueSize() {
+ int n = computeVarintSize(updateIndexDelta);
+ switch (valueType()) {
+ case VALUE_NONE:
+ return n;
+ case VALUE_1ID:
+ return n + OBJECT_ID_LENGTH;
+ case VALUE_2ID:
+ return n + 2 * OBJECT_ID_LENGTH;
+ case VALUE_SYMREF:
+ if (ref.isSymbolic()) {
+ int nameLen = nameUtf8(ref.getTarget()).length;
+ return n + computeVarintSize(nameLen) + nameLen;
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ @Override
+ void writeValue(ReftableOutputStream os) throws IOException {
+ os.writeVarint(updateIndexDelta);
+ switch (valueType()) {
+ case VALUE_NONE:
+ return;
+
+ case VALUE_1ID: {
+ ObjectId id1 = ref.getObjectId();
+ if (!ref.isPeeled()) {
+ throw new IOException(JGitText.get().peeledRefIsRequired);
+ } else if (id1 == null) {
+ throw new IOException(JGitText.get().invalidId0);
+ }
+ os.writeId(id1);
+ return;
+ }
+
+ case VALUE_2ID: {
+ ObjectId id1 = ref.getObjectId();
+ ObjectId id2 = ref.getPeeledObjectId();
+ if (!ref.isPeeled()) {
+ throw new IOException(JGitText.get().peeledRefIsRequired);
+ } else if (id1 == null || id2 == null) {
+ throw new IOException(JGitText.get().invalidId0);
+ }
+ os.writeId(id1);
+ os.writeId(id2);
+ return;
+ }
+
+ case VALUE_SYMREF:
+ if (ref.isSymbolic()) {
+ os.writeVarintString(ref.getTarget().getName());
+ return;
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ private static byte[] nameUtf8(Ref ref) {
+ return ref.getName().getBytes(UTF_8);
+ }
+ }
+
+ static class ObjEntry extends Entry {
+ final LongList blockPos;
+
+ ObjEntry(int idLen, ObjectId id, LongList blockPos) {
+ super(key(idLen, id));
+ this.blockPos = blockPos;
+ }
+
+ private static byte[] key(int idLen, ObjectId id) {
+ byte[] key = new byte[OBJECT_ID_LENGTH];
+ id.copyRawTo(key, 0);
+ if (idLen < OBJECT_ID_LENGTH) {
+ return Arrays.copyOf(key, idLen);
+ }
+ return key;
+ }
+
+ void markScanRequired() {
+ blockPos.clear();
+ }
+
+ @Override
+ byte blockType() {
+ return OBJ_BLOCK_TYPE;
+ }
+
+ @Override
+ int valueType() {
+ int cnt = blockPos.size();
+ return cnt != 0 && cnt <= VALUE_TYPE_MASK ? cnt : 0;
+ }
+
+ @Override
+ int valueSize() {
+ int cnt = blockPos.size();
+ if (cnt == 0) {
+ return computeVarintSize(0);
+ }
+
+ int n = 0;
+ if (cnt > VALUE_TYPE_MASK) {
+ n += computeVarintSize(cnt);
+ }
+ n += computeVarintSize(blockPos.get(0));
+ for (int j = 1; j < cnt; j++) {
+ long prior = blockPos.get(j - 1);
+ long b = blockPos.get(j);
+ n += computeVarintSize(b - prior);
+ }
+ return n;
+ }
+
+ @Override
+ void writeValue(ReftableOutputStream os) throws IOException {
+ int cnt = blockPos.size();
+ if (cnt == 0) {
+ os.writeVarint(0);
+ return;
+ }
+
+ if (cnt > VALUE_TYPE_MASK) {
+ os.writeVarint(cnt);
+ }
+ os.writeVarint(blockPos.get(0));
+ for (int j = 1; j < cnt; j++) {
+ long prior = blockPos.get(j - 1);
+ long b = blockPos.get(j);
+ os.writeVarint(b - prior);
+ }
+ }
+ }
+
+ static class DeleteLogEntry extends Entry {
+ DeleteLogEntry(String refName, long updateIndex) {
+ super(LogEntry.key(refName, updateIndex));
+ }
+
+ @Override
+ byte blockType() {
+ return LOG_BLOCK_TYPE;
+ }
+
+ @Override
+ int valueType() {
+ return LOG_NONE;
+ }
+
+ @Override
+ int valueSize() {
+ return 0;
+ }
+
+ @Override
+ void writeValue(ReftableOutputStream os) {
+ // Nothing in a delete log record.
+ }
+ }
+
+ static class LogEntry extends Entry {
+ final ObjectId oldId;
+ final ObjectId newId;
+ final long timeSecs;
+ final short tz;
+ final byte[] name;
+ final byte[] email;
+ final byte[] msg;
+
+ LogEntry(String refName, long updateIndex, PersonIdent who,
+ ObjectId oldId, ObjectId newId, String message) {
+ super(key(refName, updateIndex));
+
+ this.oldId = oldId;
+ this.newId = newId;
+ this.timeSecs = who.getWhen().getTime() / 1000L;
+ this.tz = (short) who.getTimeZoneOffset();
+ this.name = who.getName().getBytes(UTF_8);
+ this.email = who.getEmailAddress().getBytes(UTF_8);
+ this.msg = message.getBytes(UTF_8);
+ }
+
+ static byte[] key(String ref, long index) {
+ byte[] name = ref.getBytes(UTF_8);
+ byte[] key = Arrays.copyOf(name, name.length + 1 + 8);
+ NB.encodeInt64(key, key.length - 8, reverseUpdateIndex(index));
+ return key;
+ }
+
+ @Override
+ byte blockType() {
+ return LOG_BLOCK_TYPE;
+ }
+
+ @Override
+ int valueType() {
+ return LOG_DATA;
+ }
+
+ @Override
+ int valueSize() {
+ return 2 * OBJECT_ID_LENGTH
+ + computeVarintSize(name.length) + name.length
+ + computeVarintSize(email.length) + email.length
+ + computeVarintSize(timeSecs)
+ + 2 // tz
+ + computeVarintSize(msg.length) + msg.length;
+ }
+
+ @Override
+ void writeValue(ReftableOutputStream os) {
+ os.writeId(oldId);
+ os.writeId(newId);
+ os.writeVarintString(name);
+ os.writeVarintString(email);
+ os.writeVarint(timeSecs);
+ os.writeInt16(tz);
+ os.writeVarintString(msg);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/EmptyLogCursor.java
similarity index 76%
copy from org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/EmptyLogCursor.java
index 98a2a94..d774589 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/EmptyLogCursor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011, Google Inc.
+ * Copyright (C) 2017, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -41,20 +41,36 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.eclipse.jgit.internal.storage.dfs;
+package org.eclipse.jgit.internal.storage.reftable;
-import java.util.concurrent.atomic.AtomicLong;
+import java.io.IOException;
-final class DfsPackKey {
- final int hash;
+import org.eclipse.jgit.lib.ReflogEntry;
- final AtomicLong cachedSize;
+/** Empty {@link LogCursor} with no results. */
+class EmptyLogCursor extends LogCursor {
+ @Override
+ public boolean next() throws IOException {
+ return false;
+ }
- DfsPackKey() {
- // Multiply by 31 here so we can more directly combine with another
- // value without doing the multiply there.
- //
- hash = System.identityHashCode(this) * 31;
- cachedSize = new AtomicLong();
+ @Override
+ public String getRefName() {
+ return null;
+ }
+
+ @Override
+ public long getUpdateIndex() {
+ return 0;
+ }
+
+ @Override
+ public ReflogEntry getReflogEntry() {
+ return null;
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/LogCursor.java
similarity index 69%
copy from org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/LogCursor.java
index 98a2a94..c19968c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/LogCursor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011, Google Inc.
+ * Copyright (C) 2017, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -41,20 +41,32 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.eclipse.jgit.internal.storage.dfs;
+package org.eclipse.jgit.internal.storage.reftable;
-import java.util.concurrent.atomic.AtomicLong;
+import java.io.IOException;
-final class DfsPackKey {
- final int hash;
+import org.eclipse.jgit.lib.ReflogEntry;
- final AtomicLong cachedSize;
+/** Iterator over logs inside a {@link Reftable}. */
+public abstract class LogCursor implements AutoCloseable {
+ /**
+ * Check if another log record is available.
+ *
+ * @return {@code true} if there is another result.
+ * @throws IOException
+ * logs cannot be read.
+ */
+ public abstract boolean next() throws IOException;
- DfsPackKey() {
- // Multiply by 31 here so we can more directly combine with another
- // value without doing the multiply there.
- //
- hash = System.identityHashCode(this) * 31;
- cachedSize = new AtomicLong();
- }
+ /** @return name of the current reference. */
+ public abstract String getRefName();
+
+ /** @return identifier of the transaction that created the log record. */
+ public abstract long getUpdateIndex();
+
+ /** @return current log entry. */
+ public abstract ReflogEntry getReflogEntry();
+
+ @Override
+ public abstract void close();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java
new file mode 100644
index 0000000..9fc6ae2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.reftable;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.PriorityQueue;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.ReflogEntry;
+
+/**
+ * Merges multiple reference tables together.
+ * <p>
+ * A {@link MergedReftable} merge-joins multiple {@link ReftableReader} on the
+ * fly. Tables higher/later in the stack shadow lower/earlier tables, hiding
+ * references that been updated/replaced.
+ * <p>
+ * By default deleted references are skipped and not returned to the caller.
+ * {@link #setIncludeDeletes(boolean)} can be used to modify this behavior if
+ * the caller needs to preserve deletions during partial compaction.
+ * <p>
+ * A {@code MergedReftable} is not thread-safe.
+ */
+public class MergedReftable extends Reftable {
+ private final Reftable[] tables;
+
+ /**
+ * Initialize a merged table reader.
+ * <p>
+ * The tables in {@code tableStack} will be closed when this
+ * {@code MergedReftable} is closed.
+ *
+ * @param tableStack
+ * stack of tables to read from. The base of the stack is at
+ * index 0, the most recent should be at the top of the stack at
+ * {@code tableStack.size() - 1}. The top of the stack (higher
+ * index) shadows the base of the stack (lower index).
+ */
+ public MergedReftable(List<Reftable> tableStack) {
+ tables = tableStack.toArray(new Reftable[0]);
+
+ // Tables must expose deletes to this instance to correctly
+ // shadow references from lower tables.
+ for (Reftable t : tables) {
+ t.setIncludeDeletes(true);
+ }
+ }
+
+ @Override
+ public RefCursor allRefs() throws IOException {
+ MergedRefCursor m = new MergedRefCursor();
+ for (int i = 0; i < tables.length; i++) {
+ m.add(new RefQueueEntry(tables[i].allRefs(), i));
+ }
+ return m;
+ }
+
+ @Override
+ public RefCursor seekRef(String name) throws IOException {
+ MergedRefCursor m = new MergedRefCursor();
+ for (int i = 0; i < tables.length; i++) {
+ m.add(new RefQueueEntry(tables[i].seekRef(name), i));
+ }
+ return m;
+ }
+
+ @Override
+ public RefCursor byObjectId(AnyObjectId name) throws IOException {
+ MergedRefCursor m = new MergedRefCursor();
+ for (int i = 0; i < tables.length; i++) {
+ m.add(new RefQueueEntry(tables[i].byObjectId(name), i));
+ }
+ return m;
+ }
+
+ @Override
+ public LogCursor allLogs() throws IOException {
+ MergedLogCursor m = new MergedLogCursor();
+ for (int i = 0; i < tables.length; i++) {
+ m.add(new LogQueueEntry(tables[i].allLogs(), i));
+ }
+ return m;
+ }
+
+ @Override
+ public LogCursor seekLog(String refName, long updateIdx)
+ throws IOException {
+ MergedLogCursor m = new MergedLogCursor();
+ for (int i = 0; i < tables.length; i++) {
+ m.add(new LogQueueEntry(tables[i].seekLog(refName, updateIdx), i));
+ }
+ return m;
+ }
+
+ @Override
+ public void close() throws IOException {
+ for (Reftable t : tables) {
+ t.close();
+ }
+ }
+
+ int queueSize() {
+ return Math.max(1, tables.length);
+ }
+
+ private class MergedRefCursor extends RefCursor {
+ private final PriorityQueue<RefQueueEntry> queue;
+ private RefQueueEntry head;
+ private Ref ref;
+ private long updateIndex;
+
+ MergedRefCursor() {
+ queue = new PriorityQueue<>(queueSize(), RefQueueEntry::compare);
+ }
+
+ void add(RefQueueEntry t) throws IOException {
+ // Common case is many iterations over the same RefQueueEntry
+ // for the bottom of the stack (scanning all refs). Its almost
+ // always less than the top of the queue. Avoid the queue's
+ // O(log N) insertion and removal costs for this common case.
+ if (!t.rc.next()) {
+ t.rc.close();
+ } else if (head == null) {
+ RefQueueEntry p = queue.peek();
+ if (p == null || RefQueueEntry.compare(t, p) < 0) {
+ head = t;
+ } else {
+ head = queue.poll();
+ queue.add(t);
+ }
+ } else if (RefQueueEntry.compare(t, head) > 0) {
+ queue.add(t);
+ } else {
+ queue.add(head);
+ head = t;
+ }
+ }
+
+ @Override
+ public boolean next() throws IOException {
+ for (;;) {
+ RefQueueEntry t = poll();
+ if (t == null) {
+ return false;
+ }
+
+ ref = t.rc.getRef();
+ updateIndex = t.rc.getUpdateIndex();
+ boolean include = includeDeletes || !t.rc.wasDeleted();
+ skipShadowedRefs(ref.getName());
+ add(t);
+ if (include) {
+ return true;
+ }
+ }
+ }
+
+ private RefQueueEntry poll() {
+ RefQueueEntry e = head;
+ if (e != null) {
+ head = null;
+ return e;
+ }
+ return queue.poll();
+ }
+
+ private void skipShadowedRefs(String name) throws IOException {
+ for (;;) {
+ RefQueueEntry t = head != null ? head : queue.peek();
+ if (t != null && name.equals(t.name())) {
+ add(poll());
+ } else {
+ break;
+ }
+ }
+ }
+
+ @Override
+ public Ref getRef() {
+ return ref;
+ }
+
+ @Override
+ public long getUpdateIndex() {
+ return updateIndex;
+ }
+
+ @Override
+ public void close() {
+ if (head != null) {
+ head.rc.close();
+ head = null;
+ }
+ while (!queue.isEmpty()) {
+ queue.remove().rc.close();
+ }
+ }
+ }
+
+ private static class RefQueueEntry {
+ static int compare(RefQueueEntry a, RefQueueEntry b) {
+ int cmp = a.name().compareTo(b.name());
+ if (cmp == 0) {
+ // higher updateIndex shadows lower updateIndex.
+ cmp = Long.signum(b.updateIndex() - a.updateIndex());
+ }
+ if (cmp == 0) {
+ // higher index shadows lower index, so higher index first.
+ cmp = b.stackIdx - a.stackIdx;
+ }
+ return cmp;
+ }
+
+ final RefCursor rc;
+ final int stackIdx;
+
+ RefQueueEntry(RefCursor rc, int stackIdx) {
+ this.rc = rc;
+ this.stackIdx = stackIdx;
+ }
+
+ String name() {
+ return rc.getRef().getName();
+ }
+
+ long updateIndex() {
+ return rc.getUpdateIndex();
+ }
+ }
+
+ private class MergedLogCursor extends LogCursor {
+ private final PriorityQueue<LogQueueEntry> queue;
+ private String refName;
+ private long updateIndex;
+ private ReflogEntry entry;
+
+ MergedLogCursor() {
+ queue = new PriorityQueue<>(queueSize(), LogQueueEntry::compare);
+ }
+
+ void add(LogQueueEntry t) throws IOException {
+ if (t.lc.next()) {
+ queue.add(t);
+ } else {
+ t.lc.close();
+ }
+ }
+
+ @Override
+ public boolean next() throws IOException {
+ for (;;) {
+ LogQueueEntry t = queue.poll();
+ if (t == null) {
+ return false;
+ }
+
+ refName = t.lc.getRefName();
+ updateIndex = t.lc.getUpdateIndex();
+ entry = t.lc.getReflogEntry();
+ boolean include = includeDeletes || entry != null;
+ skipShadowed(refName, updateIndex);
+ add(t);
+ if (include) {
+ return true;
+ }
+ }
+ }
+
+ private void skipShadowed(String name, long index) throws IOException {
+ for (;;) {
+ LogQueueEntry t = queue.peek();
+ if (t != null && name.equals(t.name()) && index == t.index()) {
+ add(queue.remove());
+ } else {
+ break;
+ }
+ }
+ }
+
+ @Override
+ public String getRefName() {
+ return refName;
+ }
+
+ @Override
+ public long getUpdateIndex() {
+ return updateIndex;
+ }
+
+ @Override
+ public ReflogEntry getReflogEntry() {
+ return entry;
+ }
+
+ @Override
+ public void close() {
+ while (!queue.isEmpty()) {
+ queue.remove().lc.close();
+ }
+ }
+ }
+
+ private static class LogQueueEntry {
+ static int compare(LogQueueEntry a, LogQueueEntry b) {
+ int cmp = a.name().compareTo(b.name());
+ if (cmp == 0) {
+ // higher update index sorts first.
+ cmp = Long.signum(b.index() - a.index());
+ }
+ if (cmp == 0) {
+ // higher index comes first.
+ cmp = b.stackIdx - a.stackIdx;
+ }
+ return cmp;
+ }
+
+ final LogCursor lc;
+ final int stackIdx;
+
+ LogQueueEntry(LogCursor lc, int stackIdx) {
+ this.lc = lc;
+ this.stackIdx = stackIdx;
+ }
+
+ String name() {
+ return lc.getRefName();
+ }
+
+ long index() {
+ return lc.getUpdateIndex();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java
similarity index 67%
copy from org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java
index 98a2a94..d8e9c60 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011, Google Inc.
+ * Copyright (C) 2017, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -41,20 +41,35 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.eclipse.jgit.internal.storage.dfs;
+package org.eclipse.jgit.internal.storage.reftable;
-import java.util.concurrent.atomic.AtomicLong;
+import java.io.IOException;
-final class DfsPackKey {
- final int hash;
+import org.eclipse.jgit.lib.Ref;
- final AtomicLong cachedSize;
+/** Iterator over references inside a {@link Reftable}. */
+public abstract class RefCursor implements AutoCloseable {
+ /**
+ * Check if another reference is available.
+ *
+ * @return {@code true} if there is another result.
+ * @throws IOException
+ * references cannot be read.
+ */
+ public abstract boolean next() throws IOException;
- DfsPackKey() {
- // Multiply by 31 here so we can more directly combine with another
- // value without doing the multiply there.
- //
- hash = System.identityHashCode(this) * 31;
- cachedSize = new AtomicLong();
+ /** @return reference at the current position. */
+ public abstract Ref getRef();
+
+ /** @return updateIndex that last modified the current reference, */
+ public abstract long getUpdateIndex();
+
+ /** @return {@code true} if the current reference was deleted. */
+ public boolean wasDeleted() {
+ Ref r = getRef();
+ return r.getStorage() == Ref.Storage.NEW && r.getObjectId() == null;
}
+
+ @Override
+ public abstract void close();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java
new file mode 100644
index 0000000..1189ed3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.reftable;
+
+import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.storage.io.BlockSource;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.SymbolicRef;
+
+/** Abstract table of references. */
+public abstract class Reftable implements AutoCloseable {
+ /**
+ * @param refs
+ * references to convert into a reftable; may be empty.
+ * @return a reader for the supplied references.
+ */
+ public static Reftable from(Collection<Ref> refs) {
+ try {
+ ReftableConfig cfg = new ReftableConfig();
+ cfg.setIndexObjects(false);
+ cfg.setAlignBlocks(false);
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ new ReftableWriter()
+ .setConfig(cfg)
+ .begin(buf)
+ .sortAndWriteRefs(refs)
+ .finish();
+ return new ReftableReader(BlockSource.from(buf.toByteArray()));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** {@code true} if deletions should be included in results. */
+ protected boolean includeDeletes;
+
+ /**
+ * @param deletes
+ * if {@code true} deleted references will be returned. If
+ * {@code false} (default behavior), deleted references will be
+ * skipped, and not returned.
+ */
+ public void setIncludeDeletes(boolean deletes) {
+ includeDeletes = deletes;
+ }
+
+ /**
+ * Seek to the first reference, to iterate in order.
+ *
+ * @return cursor to iterate.
+ * @throws IOException
+ * if references cannot be read.
+ */
+ public abstract RefCursor allRefs() throws IOException;
+
+ /**
+ * Seek either to a reference, or a reference subtree.
+ * <p>
+ * If {@code refName} ends with {@code "/"} the method will seek to the
+ * subtree of all references starting with {@code refName} as a prefix. If
+ * no references start with this prefix, an empty cursor is returned.
+ * <p>
+ * Otherwise exactly {@code refName} will be looked for. If present, the
+ * returned cursor will iterate exactly one entry. If not found, an empty
+ * cursor is returned.
+ *
+ * @param refName
+ * reference name or subtree to find.
+ * @return cursor to iterate; empty cursor if no references match.
+ * @throws IOException
+ * if references cannot be read.
+ */
+ public abstract RefCursor seekRef(String refName) throws IOException;
+
+ /**
+ * Match references pointing to a specific object.
+ *
+ * @param id
+ * object to find.
+ * @return cursor to iterate; empty cursor if no references match.
+ * @throws IOException
+ * if references cannot be read.
+ */
+ public abstract RefCursor byObjectId(AnyObjectId id) throws IOException;
+
+ /**
+ * Seek reader to read log records.
+ *
+ * @return cursor to iterate; empty cursor if no logs are present.
+ * @throws IOException
+ * if logs cannot be read.
+ */
+ public abstract LogCursor allLogs() throws IOException;
+
+ /**
+ * Read a single reference's log.
+ *
+ * @param refName
+ * exact name of the reference whose log to read.
+ * @return cursor to iterate; empty cursor if no logs match.
+ * @throws IOException
+ * if logs cannot be read.
+ */
+ public LogCursor seekLog(String refName) throws IOException {
+ return seekLog(refName, Long.MAX_VALUE);
+ }
+
+ /**
+ * Seek to an update index in a reference's log.
+ *
+ * @param refName
+ * exact name of the reference whose log to read.
+ * @param updateIndex
+ * most recent index to return first in the log cursor. Log
+ * records at or before {@code updateIndex} will be returned.
+ * @return cursor to iterate; empty cursor if no logs match.
+ * @throws IOException
+ * if logs cannot be read.
+ */
+ public abstract LogCursor seekLog(String refName, long updateIndex)
+ throws IOException;
+
+ /**
+ * Lookup a reference, or null if not found.
+ *
+ * @param refName
+ * reference name to find.
+ * @return the reference, or {@code null} if not found.
+ * @throws IOException
+ * if references cannot be read.
+ */
+ @Nullable
+ public Ref exactRef(String refName) throws IOException {
+ try (RefCursor rc = seekRef(refName)) {
+ return rc.next() ? rc.getRef() : null;
+ }
+ }
+
+ /**
+ * Test if a reference or reference subtree exists.
+ * <p>
+ * If {@code refName} ends with {@code "/"}, the method tests if any
+ * reference starts with {@code refName} as a prefix.
+ * <p>
+ * Otherwise, the method checks if {@code refName} exists.
+ *
+ * @param refName
+ * reference name or subtree to find.
+ * @return {@code true} if the reference exists, or at least one reference
+ * exists in the subtree.
+ * @throws IOException
+ * if references cannot be read.
+ */
+ public boolean hasRef(String refName) throws IOException {
+ try (RefCursor rc = seekRef(refName)) {
+ return rc.next();
+ }
+ }
+
+ /**
+ * Test if any reference directly refers to the object.
+ *
+ * @param id
+ * ObjectId to find.
+ * @return {@code true} if any reference exists directly referencing
+ * {@code id}, or a annotated tag that peels to {@code id}.
+ * @throws IOException
+ * if references cannot be read.
+ */
+ public boolean hasId(AnyObjectId id) throws IOException {
+ try (RefCursor rc = byObjectId(id)) {
+ return rc.next();
+ }
+ }
+
+ /**
+ * Resolve a symbolic reference to populate its value.
+ *
+ * @param symref
+ * reference to resolve.
+ * @return resolved {@code symref}, or {@code null}.
+ * @throws IOException
+ * if references cannot be read.
+ */
+ @Nullable
+ public Ref resolve(Ref symref) throws IOException {
+ return resolve(symref, 0);
+ }
+
+ private Ref resolve(Ref ref, int depth) throws IOException {
+ if (!ref.isSymbolic()) {
+ return ref;
+ }
+
+ Ref dst = ref.getTarget();
+ if (MAX_SYMBOLIC_REF_DEPTH <= depth) {
+ return null; // claim it doesn't exist
+ }
+
+ dst = exactRef(dst.getName());
+ if (dst == null) {
+ return ref;
+ }
+
+ dst = resolve(dst, depth + 1);
+ if (dst == null) {
+ return null; // claim it doesn't exist
+ }
+ return new SymbolicRef(ref.getName(), dst);
+ }
+
+ @Override
+ public abstract void close() throws IOException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java
new file mode 100644
index 0000000..c221577
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.reftable;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.internal.storage.reftable.ReftableWriter.Stats;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.ReflogEntry;
+
+/**
+ * Merges reftables and compacts them into a single output.
+ * <p>
+ * For a partial compaction callers should {@link #setIncludeDeletes(boolean)}
+ * to {@code true} to ensure the new reftable continues to use a delete marker
+ * to shadow any lower reftable that may have the reference present.
+ * <p>
+ * By default all log entries within the range defined by
+ * {@link #setMinUpdateIndex(long)} and {@link #setMaxUpdateIndex(long)} are
+ * copied, even if no references in the output file match the log records.
+ * Callers may truncate the log to a more recent time horizon with
+ * {@link #setOldestReflogTimeMillis(long)}, or disable the log altogether with
+ * {@code setOldestReflogTimeMillis(Long.MAX_VALUE)}.
+ */
+public class ReftableCompactor {
+ private final ReftableWriter writer = new ReftableWriter();
+ private final ArrayDeque<Reftable> tables = new ArrayDeque<>();
+
+ private long compactBytesLimit;
+ private long bytesToCompact;
+ private boolean includeDeletes;
+ private long minUpdateIndex;
+ private long maxUpdateIndex;
+ private long oldestReflogTimeMillis;
+ private Stats stats;
+
+ /**
+ * @param cfg
+ * configuration for the reftable.
+ * @return {@code this}
+ */
+ public ReftableCompactor setConfig(ReftableConfig cfg) {
+ writer.setConfig(cfg);
+ return this;
+ }
+
+ /**
+ * @param bytes
+ * limit on number of bytes from source tables to compact.
+ * @return {@code this}
+ */
+ public ReftableCompactor setCompactBytesLimit(long bytes) {
+ compactBytesLimit = bytes;
+ return this;
+ }
+
+ /**
+ * @param deletes
+ * {@code true} to include deletions in the output, which may be
+ * necessary for partial compaction.
+ * @return {@code this}
+ */
+ public ReftableCompactor setIncludeDeletes(boolean deletes) {
+ includeDeletes = deletes;
+ return this;
+ }
+
+ /**
+ * @param min
+ * the minimum update index for log entries that appear in the
+ * compacted reftable. This should be 1 higher than the prior
+ * reftable's {@code maxUpdateIndex} if this table will be used
+ * in a stack.
+ * @return {@code this}
+ */
+ public ReftableCompactor setMinUpdateIndex(long min) {
+ minUpdateIndex = min;
+ return this;
+ }
+
+ /**
+ * @param max
+ * the maximum update index for log entries that appear in the
+ * compacted reftable. This should be at least 1 higher than the
+ * prior reftable's {@code maxUpdateIndex} if this table will be
+ * used in a stack.
+ * @return {@code this}
+ */
+ public ReftableCompactor setMaxUpdateIndex(long max) {
+ maxUpdateIndex = max;
+ return this;
+ }
+
+ /**
+ * @param timeMillis
+ * oldest log time to preserve. Entries whose timestamps are
+ * {@code >= timeMillis} will be copied into the output file. Log
+ * entries that predate {@code timeMillis} will be discarded.
+ * Specified in Java standard milliseconds since the epoch.
+ * @return {@code this}
+ */
+ public ReftableCompactor setOldestReflogTimeMillis(long timeMillis) {
+ oldestReflogTimeMillis = timeMillis;
+ return this;
+ }
+
+ /**
+ * Add all of the tables, in the specified order.
+ * <p>
+ * Unconditionally adds all tables, ignoring the
+ * {@link #setCompactBytesLimit(long)}.
+ *
+ * @param readers
+ * tables to compact. Tables should be ordered oldest first/most
+ * recent last so that the more recent tables can shadow the
+ * older results. Caller is responsible for closing the readers.
+ * @throws IOException
+ * update indexes of a reader cannot be accessed.
+ */
+ public void addAll(List<? extends Reftable> readers) throws IOException {
+ tables.addAll(readers);
+ for (Reftable r : readers) {
+ if (r instanceof ReftableReader) {
+ adjustUpdateIndexes((ReftableReader) r);
+ }
+ }
+ }
+
+ /**
+ * Try to add this reader at the bottom of the stack.
+ * <p>
+ * A reader may be rejected by returning {@code false} if the compactor is
+ * already rewriting its {@link #setCompactBytesLimit(long)}. When this
+ * happens the caller should stop trying to add tables, and execute the
+ * compaction.
+ *
+ * @param reader
+ * the reader to insert at the bottom of the stack. Caller is
+ * responsible for closing the reader.
+ * @return {@code true} if the compactor accepted this table; {@code false}
+ * if the compactor has reached its limit.
+ * @throws IOException
+ * if size of {@code reader}, or its update indexes cannot be read.
+ */
+ public boolean tryAddFirst(ReftableReader reader) throws IOException {
+ long sz = reader.size();
+ if (compactBytesLimit > 0 && bytesToCompact + sz > compactBytesLimit) {
+ return false;
+ }
+ bytesToCompact += sz;
+ adjustUpdateIndexes(reader);
+ tables.addFirst(reader);
+ return true;
+ }
+
+ private void adjustUpdateIndexes(ReftableReader reader) throws IOException {
+ if (minUpdateIndex == 0) {
+ minUpdateIndex = reader.minUpdateIndex();
+ } else {
+ minUpdateIndex = Math.min(minUpdateIndex, reader.minUpdateIndex());
+ }
+ maxUpdateIndex = Math.max(maxUpdateIndex, reader.maxUpdateIndex());
+ }
+
+ /**
+ * Write a compaction to {@code out}.
+ *
+ * @param out
+ * stream to write the compacted tables to. Caller is responsible
+ * for closing {@code out}.
+ * @throws IOException
+ * if tables cannot be read, or cannot be written.
+ */
+ public void compact(OutputStream out) throws IOException {
+ MergedReftable mr = new MergedReftable(new ArrayList<>(tables));
+ mr.setIncludeDeletes(includeDeletes);
+
+ writer.setMinUpdateIndex(minUpdateIndex);
+ writer.setMaxUpdateIndex(maxUpdateIndex);
+ writer.begin(out);
+ mergeRefs(mr);
+ mergeLogs(mr);
+ writer.finish();
+ stats = writer.getStats();
+ }
+
+ /** @return statistics of the last written reftable. */
+ public Stats getStats() {
+ return stats;
+ }
+
+ private void mergeRefs(MergedReftable mr) throws IOException {
+ try (RefCursor rc = mr.allRefs()) {
+ while (rc.next()) {
+ writer.writeRef(rc.getRef(), rc.getUpdateIndex());
+ }
+ }
+ }
+
+ private void mergeLogs(MergedReftable mr) throws IOException {
+ if (oldestReflogTimeMillis == Long.MAX_VALUE) {
+ return;
+ }
+
+ try (LogCursor lc = mr.allLogs()) {
+ while (lc.next()) {
+ long updateIndex = lc.getUpdateIndex();
+ if (updateIndex < minUpdateIndex
+ || updateIndex > maxUpdateIndex) {
+ // Cannot merge log records outside the header's range.
+ continue;
+ }
+
+ String refName = lc.getRefName();
+ ReflogEntry log = lc.getReflogEntry();
+ if (log == null) {
+ if (includeDeletes) {
+ writer.deleteLog(refName, updateIndex);
+ }
+ continue;
+ }
+
+ PersonIdent who = log.getWho();
+ if (who.getWhen().getTime() >= oldestReflogTimeMillis) {
+ writer.writeLog(
+ refName,
+ updateIndex,
+ who,
+ log.getOldId(),
+ log.getNewId(),
+ log.getComment());
+ }
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableConfig.java
new file mode 100644
index 0000000..f7a1fbe
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableConfig.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.reftable;
+
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_BLOCK_SIZE;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+
+/** Configuration used by a reftable writer when constructing the stream. */
+public class ReftableConfig {
+ private int refBlockSize = 4 << 10;
+ private int logBlockSize;
+ private int restartInterval;
+ private int maxIndexLevels;
+ private boolean alignBlocks = true;
+ private boolean indexObjects = true;
+
+ /** Create a default configuration. */
+ public ReftableConfig() {
+ }
+
+ /**
+ * Create a configuration honoring the repository's settings.
+ *
+ * @param db
+ * the repository to read settings from. The repository is not
+ * retained by the new configuration, instead its settings are
+ * copied during the constructor.
+ */
+ public ReftableConfig(Repository db) {
+ fromConfig(db.getConfig());
+ }
+
+ /**
+ * Create a configuration honoring settings in a {@link Config}.
+ *
+ * @param cfg
+ * the source to read settings from. The source is not retained
+ * by the new configuration, instead its settings are copied
+ * during the constructor.
+ */
+ public ReftableConfig(Config cfg) {
+ fromConfig(cfg);
+ }
+
+ /**
+ * Copy an existing configuration to a new instance.
+ *
+ * @param cfg
+ * the source configuration to copy from.
+ */
+ public ReftableConfig(ReftableConfig cfg) {
+ this.refBlockSize = cfg.refBlockSize;
+ this.logBlockSize = cfg.logBlockSize;
+ this.restartInterval = cfg.restartInterval;
+ this.maxIndexLevels = cfg.maxIndexLevels;
+ this.alignBlocks = cfg.alignBlocks;
+ this.indexObjects = cfg.indexObjects;
+ }
+
+ /** @return desired output block size for references, in bytes */
+ public int getRefBlockSize() {
+ return refBlockSize;
+ }
+
+ /**
+ * @param szBytes
+ * desired output block size for references, in bytes.
+ */
+ public void setRefBlockSize(int szBytes) {
+ if (szBytes > MAX_BLOCK_SIZE) {
+ throw new IllegalArgumentException();
+ }
+ refBlockSize = Math.max(0, szBytes);
+ }
+
+ /**
+ * @return desired output block size for log entries, in bytes. If 0 the
+ * writer will default to {@code 2 * getRefBlockSize()}.
+ */
+ public int getLogBlockSize() {
+ return logBlockSize;
+ }
+
+ /**
+ * @param szBytes
+ * desired output block size for log entries, in bytes. If 0 will
+ * default to {@code 2 * getRefBlockSize()}.
+ */
+ public void setLogBlockSize(int szBytes) {
+ if (szBytes > MAX_BLOCK_SIZE) {
+ throw new IllegalArgumentException();
+ }
+ logBlockSize = Math.max(0, szBytes);
+ }
+
+ /** @return number of references between binary search markers. */
+ public int getRestartInterval() {
+ return restartInterval;
+ }
+
+ /**
+ * @param interval
+ * number of references between binary search markers. If
+ * {@code interval} is 0 (default), the writer will select a
+ * default value based on the block size.
+ */
+ public void setRestartInterval(int interval) {
+ restartInterval = Math.max(0, interval);
+ }
+
+ /** @return maximum depth of the index; 0 for unlimited. */
+ public int getMaxIndexLevels() {
+ return maxIndexLevels;
+ }
+
+ /**
+ * @param levels
+ * maximum number of levels to use in indexes. Lower levels of
+ * the index respect {@link #getRefBlockSize()}, and the highest
+ * level may exceed that if the number of levels is limited.
+ */
+ public void setMaxIndexLevels(int levels) {
+ maxIndexLevels = Math.max(0, levels);
+ }
+
+ /** @return {@code true} if the writer should align blocks. */
+ public boolean isAlignBlocks() {
+ return alignBlocks;
+ }
+
+ /**
+ * @param align
+ * if {@code true} blocks are written aligned to multiples of
+ * {@link #getRefBlockSize()}. May increase file size due to NUL
+ * padding bytes added between blocks. Default is {@code true}.
+ */
+ public void setAlignBlocks(boolean align) {
+ alignBlocks = align;
+ }
+
+ /** @return {@code true} if the writer should index object to ref. */
+ public boolean isIndexObjects() {
+ return indexObjects;
+ }
+
+ /**
+ * @param index
+ * if {@code true} the reftable may include additional storage to
+ * efficiently map from {@code ObjectId} to reference names. By
+ * default, {@code true}.
+ */
+ public void setIndexObjects(boolean index) {
+ indexObjects = index;
+ }
+
+ /**
+ * Update properties by setting fields from the configuration.
+ *
+ * If a property's corresponding variable is not defined in the supplied
+ * configuration, then it is left unmodified.
+ *
+ * @param rc
+ * configuration to read properties from.
+ */
+ public void fromConfig(Config rc) {
+ refBlockSize = rc.getInt("reftable", "blockSize", refBlockSize); //$NON-NLS-1$ //$NON-NLS-2$
+ logBlockSize = rc.getInt("reftable", "logBlockSize", logBlockSize); //$NON-NLS-1$ //$NON-NLS-2$
+ restartInterval = rc.getInt("reftable", "restartInterval", restartInterval); //$NON-NLS-1$ //$NON-NLS-2$
+ maxIndexLevels = rc.getInt("reftable", "indexLevels", maxIndexLevels); //$NON-NLS-1$ //$NON-NLS-2$
+ alignBlocks = rc.getBoolean("reftable", "alignBlocks", alignBlocks); //$NON-NLS-1$ //$NON-NLS-2$
+ indexObjects = rc.getBoolean("reftable", "indexObjects", indexObjects); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableConstants.java
similarity index 60%
copy from org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
copy to org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableConstants.java
index 98a2a94..0b89327 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackKey.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableConstants.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011, Google Inc.
+ * Copyright (C) 2017, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -41,20 +41,45 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.eclipse.jgit.internal.storage.dfs;
+package org.eclipse.jgit.internal.storage.reftable;
-import java.util.concurrent.atomic.AtomicLong;
+class ReftableConstants {
+ static final byte[] FILE_HEADER_MAGIC = { 'R', 'E', 'F', 'T' };
+ static final byte VERSION_1 = (byte) 1;
-final class DfsPackKey {
- final int hash;
+ static final int FILE_HEADER_LEN = 24;
+ static final int FILE_FOOTER_LEN = 68;
- final AtomicLong cachedSize;
+ static final byte FILE_BLOCK_TYPE = 'R';
+ static final byte REF_BLOCK_TYPE = 'r';
+ static final byte OBJ_BLOCK_TYPE = 'o';
+ static final byte LOG_BLOCK_TYPE = 'g';
+ static final byte INDEX_BLOCK_TYPE = 'i';
- DfsPackKey() {
- // Multiply by 31 here so we can more directly combine with another
- // value without doing the multiply there.
- //
- hash = System.identityHashCode(this) * 31;
- cachedSize = new AtomicLong();
+ static final int VALUE_NONE = 0x0;
+ static final int VALUE_1ID = 0x1;
+ static final int VALUE_2ID = 0x2;
+ static final int VALUE_SYMREF = 0x3;
+ static final int VALUE_TYPE_MASK = 0x7;
+
+ static final int LOG_NONE = 0x0;
+ static final int LOG_DATA = 0x1;
+
+ static final int MAX_BLOCK_SIZE = (1 << 24) - 1;
+ static final int MAX_RESTARTS = 65535;
+
+ static boolean isFileHeaderMagic(byte[] buf, int o, int n) {
+ return (n - o) >= FILE_HEADER_MAGIC.length
+ && buf[o + 0] == FILE_HEADER_MAGIC[0]
+ && buf[o + 1] == FILE_HEADER_MAGIC[1]
+ && buf[o + 2] == FILE_HEADER_MAGIC[2]
+ && buf[o + 3] == FILE_HEADER_MAGIC[3];
+ }
+
+ static long reverseUpdateIndex(long time) {
+ return 0xffffffffffffffffL - time;
+ }
+
+ private ReftableConstants() {
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java
new file mode 100644
index 0000000..a24619b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.reftable;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.io.CountingOutputStream;
+
+/**
+ * Wrapper to assist formatting a reftable to an {@link OutputStream}.
+ * <p>
+ * Internally buffers at block size boundaries, flushing only complete blocks to
+ * the {@code OutputStream}.
+ */
+class ReftableOutputStream extends OutputStream {
+ private final byte[] tmp = new byte[10];
+ private final CountingOutputStream out;
+ private final boolean alignBlocks;
+
+ private Deflater deflater;
+ private DeflaterOutputStream compressor;
+
+ private int blockType;
+ private int blockSize;
+ private int blockStart;
+ private byte[] blockBuf;
+ private int cur;
+ private long paddingUsed;
+
+ ReftableOutputStream(OutputStream os, int bs, boolean align) {
+ blockSize = bs;
+ blockBuf = new byte[bs];
+ alignBlocks = align;
+ out = new CountingOutputStream(os);
+ }
+
+ void setBlockSize(int bs) {
+ blockSize = bs;
+ }
+
+ @Override
+ public void write(int b) {
+ ensureBytesAvailableInBlockBuf(1);
+ blockBuf[cur++] = (byte) b;
+ }
+
+ @Override
+ public void write(byte[] b, int off, int cnt) {
+ ensureBytesAvailableInBlockBuf(cnt);
+ System.arraycopy(b, off, blockBuf, cur, cnt);
+ cur += cnt;
+ }
+
+ int bytesWrittenInBlock() {
+ return cur;
+ }
+
+ int bytesAvailableInBlock() {
+ return blockSize - cur;
+ }
+
+ long paddingUsed() {
+ return paddingUsed;
+ }
+
+ /** @return bytes flushed; excludes {@link #bytesWrittenInBlock()}. */
+ long size() {
+ return out.getCount();
+ }
+
+ static int computeVarintSize(long val) {
+ int n = 1;
+ for (; (val >>>= 7) != 0; n++) {
+ val--;
+ }
+ return n;
+ }
+
+ void writeVarint(long val) {
+ int n = tmp.length;
+ tmp[--n] = (byte) (val & 0x7f);
+ while ((val >>>= 7) != 0) {
+ tmp[--n] = (byte) (0x80 | (--val & 0x7F));
+ }
+ write(tmp, n, tmp.length - n);
+ }
+
+ void writeInt16(int val) {
+ ensureBytesAvailableInBlockBuf(2);
+ NB.encodeInt16(blockBuf, cur, val);
+ cur += 2;
+ }
+
+ void writeInt24(int val) {
+ ensureBytesAvailableInBlockBuf(3);
+ NB.encodeInt24(blockBuf, cur, val);
+ cur += 3;
+ }
+
+ void writeId(ObjectId id) {
+ ensureBytesAvailableInBlockBuf(OBJECT_ID_LENGTH);
+ id.copyRawTo(blockBuf, cur);
+ cur += OBJECT_ID_LENGTH;
+ }
+
+ void writeVarintString(String s) {
+ writeVarintString(s.getBytes(UTF_8));
+ }
+
+ void writeVarintString(byte[] msg) {
+ writeVarint(msg.length);
+ write(msg, 0, msg.length);
+ }
+
+ private void ensureBytesAvailableInBlockBuf(int cnt) {
+ if (cur + cnt > blockBuf.length) {
+ int n = Math.max(cur + cnt, blockBuf.length * 2);
+ blockBuf = Arrays.copyOf(blockBuf, n);
+ }
+ }
+
+ void flushFileHeader() throws IOException {
+ if (cur == FILE_HEADER_LEN && out.getCount() == 0) {
+ out.write(blockBuf, 0, cur);
+ cur = 0;
+ }
+ }
+
+ void beginBlock(byte type) {
+ blockType = type;
+ blockStart = cur;
+ cur += 4; // reserve space for 4-byte block header.
+ }
+
+ void flushBlock() throws IOException {
+ if (cur > blockSize && blockType != INDEX_BLOCK_TYPE) {
+ throw new IOException(JGitText.get().overflowedReftableBlock);
+ }
+ NB.encodeInt32(blockBuf, blockStart, (blockType << 24) | cur);
+
+ if (blockType == LOG_BLOCK_TYPE) {
+ // Log blocks are deflated after the block header.
+ out.write(blockBuf, 0, 4);
+ if (deflater != null) {
+ deflater.reset();
+ } else {
+ deflater = new Deflater(Deflater.BEST_COMPRESSION);
+ compressor = new DeflaterOutputStream(out, deflater);
+ }
+ compressor.write(blockBuf, 4, cur - 4);
+ compressor.finish();
+ } else {
+ // Other blocks are uncompressed.
+ out.write(blockBuf, 0, cur);
+ }
+
+ cur = 0;
+ blockType = 0;
+ blockStart = 0;
+ }
+
+ void padBetweenBlocksToNextBlock() throws IOException {
+ if (alignBlocks) {
+ long m = size() % blockSize;
+ if (m > 0) {
+ int pad = blockSize - (int) m;
+ ensureBytesAvailableInBlockBuf(pad);
+ Arrays.fill(blockBuf, 0, pad, (byte) 0);
+ out.write(blockBuf, 0, pad);
+ paddingUsed += pad;
+ }
+ }
+ }
+
+ int estimatePadBetweenBlocks(int currentBlockSize) {
+ if (alignBlocks) {
+ long m = (size() + currentBlockSize) % blockSize;
+ return m > 0 ? blockSize - (int) m : 0;
+ }
+ return 0;
+ }
+
+ void finishFile() throws IOException {
+ // File footer doesn't need patching for the block start.
+ // Just flush what has been buffered.
+ out.write(blockBuf, 0, cur);
+ cur = 0;
+
+ if (deflater != null) {
+ deflater.end();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java
new file mode 100644
index 0000000..407a77c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java
@@ -0,0 +1,683 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.reftable;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.internal.storage.reftable.BlockReader.decodeBlockLen;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_FOOTER_LEN;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VERSION_1;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.isFileHeaderMagic;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.zip.CRC32;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.io.BlockSource;
+import org.eclipse.jgit.internal.storage.reftable.BlockWriter.LogEntry;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.util.LongList;
+import org.eclipse.jgit.util.LongMap;
+import org.eclipse.jgit.util.NB;
+
+/**
+ * Reads a reftable formatted file.
+ * <p>
+ * {@code ReftableReader} is not thread-safe. Concurrent readers need their own
+ * instance to read from the same file.
+ */
+public class ReftableReader extends Reftable {
+ private final BlockSource src;
+
+ private int blockSize = -1;
+ private long minUpdateIndex;
+ private long maxUpdateIndex;
+
+ private long refEnd;
+ private long objPosition;
+ private long objEnd;
+ private long logPosition;
+ private long logEnd;
+ private int objIdLen;
+
+ private long refIndexPosition = -1;
+ private long objIndexPosition = -1;
+ private long logIndexPosition = -1;
+
+ private BlockReader refIndex;
+ private BlockReader objIndex;
+ private BlockReader logIndex;
+ private LongMap<BlockReader> indexCache;
+
+ /**
+ * Initialize a new reftable reader.
+ *
+ * @param src
+ * the file content to read.
+ */
+ public ReftableReader(BlockSource src) {
+ this.src = src;
+ }
+
+ /**
+ * @return the block size in bytes chosen for this file by the writer. Most
+ * reads from the {@link BlockSource} will be aligned to the block
+ * size.
+ * @throws IOException
+ * file cannot be read.
+ */
+ public int blockSize() throws IOException {
+ if (blockSize == -1) {
+ readFileHeader();
+ }
+ return blockSize;
+ }
+
+ /**
+ * @return the minimum update index for log entries that appear in this
+ * reftable. This should be 1 higher than the prior reftable's
+ * {@code maxUpdateIndex} if this table is used in a stack.
+ * @throws IOException
+ * file cannot be read.
+ */
+ public long minUpdateIndex() throws IOException {
+ if (blockSize == -1) {
+ readFileHeader();
+ }
+ return minUpdateIndex;
+ }
+
+ /**
+ * @return the maximum update index for log entries that appear in this
+ * reftable. This should be 1 higher than the prior reftable's
+ * {@code maxUpdateIndex} if this table is used in a stack.
+ * @throws IOException
+ * file cannot be read.
+ */
+ public long maxUpdateIndex() throws IOException {
+ if (blockSize == -1) {
+ readFileHeader();
+ }
+ return maxUpdateIndex;
+ }
+
+ @Override
+ public RefCursor allRefs() throws IOException {
+ if (blockSize == -1) {
+ readFileHeader();
+ }
+
+ long end = refEnd > 0 ? refEnd : (src.size() - FILE_FOOTER_LEN);
+ src.adviseSequentialRead(0, end);
+
+ RefCursorImpl i = new RefCursorImpl(end, null, false);
+ i.block = readBlock(0, end);
+ return i;
+ }
+
+ @Override
+ public RefCursor seekRef(String refName) throws IOException {
+ initRefIndex();
+
+ byte[] key = refName.getBytes(UTF_8);
+ boolean prefix = key[key.length - 1] == '/';
+
+ RefCursorImpl i = new RefCursorImpl(refEnd, key, prefix);
+ i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd);
+ return i;
+ }
+
+ @Override
+ public RefCursor byObjectId(AnyObjectId id) throws IOException {
+ initObjIndex();
+ ObjCursorImpl i = new ObjCursorImpl(refEnd, id);
+ if (objIndex != null) {
+ i.initSeek();
+ } else {
+ i.initScan();
+ }
+ return i;
+ }
+
+ @Override
+ public LogCursor allLogs() throws IOException {
+ initLogIndex();
+ if (logPosition > 0) {
+ src.adviseSequentialRead(logPosition, logEnd);
+ LogCursorImpl i = new LogCursorImpl(logEnd, null);
+ i.block = readBlock(logPosition, logEnd);
+ return i;
+ }
+ return new EmptyLogCursor();
+ }
+
+ @Override
+ public LogCursor seekLog(String refName, long updateIndex)
+ throws IOException {
+ initLogIndex();
+ if (logPosition > 0) {
+ byte[] key = LogEntry.key(refName, updateIndex);
+ byte[] match = refName.getBytes(UTF_8);
+ LogCursorImpl i = new LogCursorImpl(logEnd, match);
+ i.block = seek(LOG_BLOCK_TYPE, key, logIndex, logPosition, logEnd);
+ return i;
+ }
+ return new EmptyLogCursor();
+ }
+
+ private BlockReader seek(byte blockType, byte[] key, BlockReader idx,
+ long startPos, long endPos) throws IOException {
+ if (idx != null) {
+ // Walk through a possibly multi-level index to a leaf block.
+ BlockReader block = idx;
+ do {
+ if (block.seekKey(key) > 0) {
+ return null;
+ }
+ long pos = block.readPositionFromIndex();
+ block = readBlock(pos, endPos);
+ } while (block.type() == INDEX_BLOCK_TYPE);
+ block.seekKey(key);
+ return block;
+ }
+ return binarySearch(blockType, key, startPos, endPos);
+ }
+
+ private BlockReader binarySearch(byte blockType, byte[] key,
+ long startPos, long endPos) throws IOException {
+ if (blockSize == 0) {
+ BlockReader b = readBlock(startPos, endPos);
+ if (blockType != b.type()) {
+ return null;
+ }
+ b.seekKey(key);
+ return b;
+ }
+
+ int low = (int) (startPos / blockSize);
+ int end = blocksIn(startPos, endPos);
+ BlockReader block = null;
+ do {
+ int mid = (low + end) >>> 1;
+ block = readBlock(((long) mid) * blockSize, endPos);
+ if (blockType != block.type()) {
+ return null;
+ }
+ int cmp = block.seekKey(key);
+ if (cmp < 0) {
+ end = mid;
+ } else if (cmp == 0) {
+ break;
+ } else /* if (cmp > 0) */ {
+ low = mid + 1;
+ }
+ } while (low < end);
+ return block;
+ }
+
+ private void readFileHeader() throws IOException {
+ readHeaderOrFooter(0, FILE_HEADER_LEN);
+ }
+
+ private void readFileFooter() throws IOException {
+ int ftrLen = FILE_FOOTER_LEN;
+ byte[] ftr = readHeaderOrFooter(src.size() - ftrLen, ftrLen);
+
+ CRC32 crc = new CRC32();
+ crc.update(ftr, 0, ftrLen - 4);
+ if (crc.getValue() != NB.decodeUInt32(ftr, ftrLen - 4)) {
+ throw new IOException(JGitText.get().invalidReftableCRC);
+ }
+
+ refIndexPosition = NB.decodeInt64(ftr, 24);
+ long p = NB.decodeInt64(ftr, 32);
+ objPosition = p >>> 5;
+ objIdLen = (int) (p & 0x1f);
+ objIndexPosition = NB.decodeInt64(ftr, 40);
+ logPosition = NB.decodeInt64(ftr, 48);
+ logIndexPosition = NB.decodeInt64(ftr, 56);
+
+ if (refIndexPosition > 0) {
+ refEnd = refIndexPosition;
+ } else if (objPosition > 0) {
+ refEnd = objPosition;
+ } else if (logPosition > 0) {
+ refEnd = logPosition;
+ } else {
+ refEnd = src.size() - ftrLen;
+ }
+
+ if (objPosition > 0) {
+ if (objIndexPosition > 0) {
+ objEnd = objIndexPosition;
+ } else if (logPosition > 0) {
+ objEnd = logPosition;
+ } else {
+ objEnd = src.size() - ftrLen;
+ }
+ }
+
+ if (logPosition > 0) {
+ if (logIndexPosition > 0) {
+ logEnd = logIndexPosition;
+ } else {
+ logEnd = src.size() - ftrLen;
+ }
+ }
+ }
+
+ private byte[] readHeaderOrFooter(long pos, int len) throws IOException {
+ ByteBuffer buf = src.read(pos, len);
+ if (buf.position() != len) {
+ throw new IOException(JGitText.get().shortReadOfBlock);
+ }
+
+ byte[] tmp = new byte[len];
+ buf.flip();
+ buf.get(tmp);
+ if (!isFileHeaderMagic(tmp, 0, len)) {
+ throw new IOException(JGitText.get().invalidReftableFile);
+ }
+
+ int v = NB.decodeInt32(tmp, 4);
+ int version = v >>> 24;
+ if (VERSION_1 != version) {
+ throw new IOException(MessageFormat.format(
+ JGitText.get().unsupportedReftableVersion,
+ Integer.valueOf(version)));
+ }
+ if (blockSize == -1) {
+ blockSize = v & 0xffffff;
+ }
+ minUpdateIndex = NB.decodeInt64(tmp, 8);
+ maxUpdateIndex = NB.decodeInt64(tmp, 16);
+ return tmp;
+ }
+
+ private void initRefIndex() throws IOException {
+ if (refIndexPosition < 0) {
+ readFileFooter();
+ }
+ if (refIndex == null && refIndexPosition > 0) {
+ refIndex = readIndex(refIndexPosition);
+ }
+ }
+
+ private void initObjIndex() throws IOException {
+ if (objIndexPosition < 0) {
+ readFileFooter();
+ }
+ if (objIndex == null && objIndexPosition > 0) {
+ objIndex = readIndex(objIndexPosition);
+ }
+ }
+
+ private void initLogIndex() throws IOException {
+ if (logIndexPosition < 0) {
+ readFileFooter();
+ }
+ if (logIndex == null && logIndexPosition > 0) {
+ logIndex = readIndex(logIndexPosition);
+ }
+ }
+
+ private BlockReader readIndex(long pos) throws IOException {
+ int sz = readBlockLen(pos);
+ BlockReader i = new BlockReader();
+ i.readBlock(src, pos, sz);
+ i.verifyIndex();
+ return i;
+ }
+
+ private int readBlockLen(long pos) throws IOException {
+ int sz = pos == 0 ? FILE_HEADER_LEN + 4 : 4;
+ ByteBuffer tmp = src.read(pos, sz);
+ if (tmp.position() < sz) {
+ throw new IOException(JGitText.get().invalidReftableFile);
+ }
+ byte[] buf;
+ if (tmp.hasArray() && tmp.arrayOffset() == 0) {
+ buf = tmp.array();
+ } else {
+ buf = new byte[sz];
+ tmp.flip();
+ tmp.get(buf);
+ }
+ if (pos == 0 && buf[FILE_HEADER_LEN] == FILE_BLOCK_TYPE) {
+ return FILE_HEADER_LEN;
+ }
+ int p = pos == 0 ? FILE_HEADER_LEN : 0;
+ return decodeBlockLen(NB.decodeInt32(buf, p));
+ }
+
+ private BlockReader readBlock(long pos, long end) throws IOException {
+ if (indexCache != null) {
+ BlockReader b = indexCache.get(pos);
+ if (b != null) {
+ return b;
+ }
+ }
+
+ int sz = blockSize;
+ if (sz == 0) {
+ sz = readBlockLen(pos);
+ } else if (pos + sz > end) {
+ sz = (int) (end - pos); // last block may omit padding.
+ }
+
+ BlockReader b = new BlockReader();
+ b.readBlock(src, pos, sz);
+ if (b.type() == INDEX_BLOCK_TYPE && !b.truncated()) {
+ if (indexCache == null) {
+ indexCache = new LongMap<>();
+ }
+ indexCache.put(pos, b);
+ }
+ return b;
+ }
+
+ private int blocksIn(long pos, long end) {
+ int blocks = (int) ((end - pos) / blockSize);
+ return end % blockSize == 0 ? blocks : (blocks + 1);
+ }
+
+ /**
+ * Get size of the reftable, in bytes.
+ *
+ * @return size of the reftable, in bytes.
+ * @throws IOException
+ * size cannot be obtained.
+ */
+ public long size() throws IOException {
+ return src.size();
+ }
+
+ @Override
+ public void close() throws IOException {
+ src.close();
+ }
+
+ private class RefCursorImpl extends RefCursor {
+ private final long scanEnd;
+ private final byte[] match;
+ private final boolean prefix;
+
+ private Ref ref;
+ private long updateIndex;
+ BlockReader block;
+
+ RefCursorImpl(long scanEnd, byte[] match, boolean prefix) {
+ this.scanEnd = scanEnd;
+ this.match = match;
+ this.prefix = prefix;
+ }
+
+ @Override
+ public boolean next() throws IOException {
+ for (;;) {
+ if (block == null || block.type() != REF_BLOCK_TYPE) {
+ return false;
+ } else if (!block.next()) {
+ long pos = block.endPosition();
+ if (pos >= scanEnd) {
+ return false;
+ }
+ block = readBlock(pos, scanEnd);
+ continue;
+ }
+
+ block.parseKey();
+ if (match != null && !block.match(match, prefix)) {
+ block.skipValue();
+ return false;
+ }
+
+ updateIndex = minUpdateIndex + block.readUpdateIndexDelta();
+ ref = block.readRef();
+ if (!includeDeletes && wasDeleted()) {
+ continue;
+ }
+ return true;
+ }
+ }
+
+ @Override
+ public Ref getRef() {
+ return ref;
+ }
+
+ @Override
+ public long getUpdateIndex() {
+ return updateIndex;
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+ }
+
+ private class LogCursorImpl extends LogCursor {
+ private final long scanEnd;
+ private final byte[] match;
+
+ private String refName;
+ private long updateIndex;
+ private ReflogEntry entry;
+ BlockReader block;
+
+ LogCursorImpl(long scanEnd, byte[] match) {
+ this.scanEnd = scanEnd;
+ this.match = match;
+ }
+
+ @Override
+ public boolean next() throws IOException {
+ for (;;) {
+ if (block == null || block.type() != LOG_BLOCK_TYPE) {
+ return false;
+ } else if (!block.next()) {
+ long pos = block.endPosition();
+ if (pos >= scanEnd) {
+ return false;
+ }
+ block = readBlock(pos, scanEnd);
+ continue;
+ }
+
+ block.parseKey();
+ if (match != null && !block.match(match, false)) {
+ block.skipValue();
+ return false;
+ }
+
+ refName = block.name();
+ updateIndex = block.readLogUpdateIndex();
+ entry = block.readLogEntry();
+ if (entry == null && !includeDeletes) {
+ continue;
+ }
+ return true;
+ }
+ }
+
+ @Override
+ public String getRefName() {
+ return refName;
+ }
+
+ @Override
+ public long getUpdateIndex() {
+ return updateIndex;
+ }
+
+ @Override
+ public ReflogEntry getReflogEntry() {
+ return entry;
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+ }
+
+ static final LongList EMPTY_LONG_LIST = new LongList(0);
+
+ private class ObjCursorImpl extends RefCursor {
+ private final long scanEnd;
+ private final ObjectId match;
+
+ private Ref ref;
+ private long updateIndex;
+ private int listIdx;
+
+ private LongList blockPos;
+ private BlockReader block;
+
+ ObjCursorImpl(long scanEnd, AnyObjectId id) {
+ this.scanEnd = scanEnd;
+ this.match = id.copy();
+ }
+
+ void initSeek() throws IOException {
+ byte[] rawId = new byte[OBJECT_ID_LENGTH];
+ match.copyRawTo(rawId, 0);
+ byte[] key = Arrays.copyOf(rawId, objIdLen);
+
+ BlockReader b = objIndex;
+ do {
+ if (b.seekKey(key) > 0) {
+ blockPos = EMPTY_LONG_LIST;
+ return;
+ }
+ long pos = b.readPositionFromIndex();
+ b = readBlock(pos, objEnd);
+ } while (b.type() == INDEX_BLOCK_TYPE);
+ b.seekKey(key);
+ while (b.next()) {
+ b.parseKey();
+ if (b.match(key, false)) {
+ blockPos = b.readBlockPositionList();
+ if (blockPos == null) {
+ initScan();
+ return;
+ }
+ break;
+ }
+ b.skipValue();
+ }
+ if (blockPos == null) {
+ blockPos = EMPTY_LONG_LIST;
+ }
+ if (blockPos.size() > 0) {
+ long pos = blockPos.get(listIdx++);
+ block = readBlock(pos, scanEnd);
+ }
+ }
+
+ void initScan() throws IOException {
+ block = readBlock(0, scanEnd);
+ }
+
+ @Override
+ public boolean next() throws IOException {
+ for (;;) {
+ if (block == null || block.type() != REF_BLOCK_TYPE) {
+ return false;
+ } else if (!block.next()) {
+ long pos;
+ if (blockPos != null) {
+ if (listIdx >= blockPos.size()) {
+ return false;
+ }
+ pos = blockPos.get(listIdx++);
+ } else {
+ pos = block.endPosition();
+ }
+ if (pos >= scanEnd) {
+ return false;
+ }
+ block = readBlock(pos, scanEnd);
+ continue;
+ }
+
+ block.parseKey();
+ updateIndex = minUpdateIndex + block.readUpdateIndexDelta();
+ ref = block.readRef();
+ ObjectId id = ref.getObjectId();
+ if (id != null && match.equals(id)
+ && (includeDeletes || !wasDeleted())) {
+ return true;
+ }
+ }
+ }
+
+ @Override
+ public Ref getRef() {
+ return ref;
+ }
+
+ @Override
+ public long getUpdateIndex() {
+ return updateIndex;
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java
new file mode 100644
index 0000000..0ac2445
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java
@@ -0,0 +1,813 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.reftable;
+
+import static java.lang.Math.log;
+import static org.eclipse.jgit.internal.storage.reftable.BlockWriter.padBetweenBlocks;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_FOOTER_LEN;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_MAGIC;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_BLOCK_SIZE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_RESTARTS;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE;
+import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VERSION_1;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.CRC32;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.storage.reftable.BlockWriter.DeleteLogEntry;
+import org.eclipse.jgit.internal.storage.reftable.BlockWriter.Entry;
+import org.eclipse.jgit.internal.storage.reftable.BlockWriter.IndexEntry;
+import org.eclipse.jgit.internal.storage.reftable.BlockWriter.LogEntry;
+import org.eclipse.jgit.internal.storage.reftable.BlockWriter.ObjEntry;
+import org.eclipse.jgit.internal.storage.reftable.BlockWriter.RefEntry;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdOwnerMap;
+import org.eclipse.jgit.lib.ObjectIdSubclassMap;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.util.LongList;
+import org.eclipse.jgit.util.NB;
+
+/**
+ * Writes a reftable formatted file.
+ * <p>
+ * A reftable can be written in a streaming fashion, provided the caller sorts
+ * all references. A {@link ReftableWriter} is single-use, and not thread-safe.
+ */
+public class ReftableWriter {
+ private ReftableConfig config;
+ private int refBlockSize;
+ private int logBlockSize;
+ private int restartInterval;
+ private int maxIndexLevels;
+ private boolean alignBlocks;
+ private boolean indexObjects;
+
+ private long minUpdateIndex;
+ private long maxUpdateIndex;
+
+ private ReftableOutputStream out;
+ private ObjectIdSubclassMap<RefList> obj2ref;
+
+ private BlockWriter cur;
+ private Section refs;
+ private Section objs;
+ private Section logs;
+ private int objIdLen;
+ private Stats stats;
+
+ /** Initialize a writer with a default configuration. */
+ public ReftableWriter() {
+ this(new ReftableConfig());
+ }
+
+ /**
+ * Initialize a writer with a specific configuration.
+ *
+ * @param cfg
+ * configuration for the writer.
+ */
+ public ReftableWriter(ReftableConfig cfg) {
+ config = cfg;
+ }
+
+ /**
+ * @param cfg
+ * configuration for the writer.
+ * @return {@code this}
+ */
+ public ReftableWriter setConfig(ReftableConfig cfg) {
+ this.config = cfg != null ? cfg : new ReftableConfig();
+ return this;
+ }
+
+ /**
+ * @param min
+ * the minimum update index for log entries that appear in this
+ * reftable. This should be 1 higher than the prior reftable's
+ * {@code maxUpdateIndex} if this table will be used in a stack.
+ * @return {@code this}
+ */
+ public ReftableWriter setMinUpdateIndex(long min) {
+ minUpdateIndex = min;
+ return this;
+ }
+
+ /**
+ * @param max
+ * the maximum update index for log entries that appear in this
+ * reftable. This should be at least 1 higher than the prior
+ * reftable's {@code maxUpdateIndex} if this table will be used
+ * in a stack.
+ * @return {@code this}
+ */
+ public ReftableWriter setMaxUpdateIndex(long max) {
+ maxUpdateIndex = max;
+ return this;
+ }
+
+ /**
+ * Begin writing the reftable.
+ *
+ * @param os
+ * stream to write the table to. Caller is responsible for
+ * closing the stream after invoking {@link #finish()}.
+ * @return {@code this}
+ * @throws IOException
+ * if reftable header cannot be written.
+ */
+ public ReftableWriter begin(OutputStream os) throws IOException {
+ refBlockSize = config.getRefBlockSize();
+ logBlockSize = config.getLogBlockSize();
+ restartInterval = config.getRestartInterval();
+ maxIndexLevels = config.getMaxIndexLevels();
+ alignBlocks = config.isAlignBlocks();
+ indexObjects = config.isIndexObjects();
+
+ if (refBlockSize <= 0) {
+ refBlockSize = 4 << 10;
+ } else if (refBlockSize > MAX_BLOCK_SIZE) {
+ throw new IllegalArgumentException();
+ }
+ if (logBlockSize <= 0) {
+ logBlockSize = 2 * refBlockSize;
+ }
+ if (restartInterval <= 0) {
+ restartInterval = refBlockSize < (60 << 10) ? 16 : 64;
+ }
+
+ out = new ReftableOutputStream(os, refBlockSize, alignBlocks);
+ refs = new Section(REF_BLOCK_TYPE);
+ if (indexObjects) {
+ obj2ref = new ObjectIdSubclassMap<>();
+ }
+ writeFileHeader();
+ return this;
+ }
+
+ /**
+ * Sort a collection of references and write them to the reftable.
+ *
+ * @param refsToPack
+ * references to sort and write.
+ * @return {@code this}
+ * @throws IOException
+ * if reftable cannot be written.
+ */
+ public ReftableWriter sortAndWriteRefs(Collection<Ref> refsToPack)
+ throws IOException {
+ Iterator<RefEntry> itr = refsToPack.stream()
+ .map(r -> new RefEntry(r, maxUpdateIndex - minUpdateIndex))
+ .sorted(Entry::compare)
+ .iterator();
+ while (itr.hasNext()) {
+ RefEntry entry = itr.next();
+ long blockPos = refs.write(entry);
+ indexRef(entry.ref, blockPos);
+ }
+ return this;
+ }
+
+ /**
+ * Write one reference to the reftable.
+ * <p>
+ * References must be passed in sorted order.
+ *
+ * @param ref
+ * the reference to store.
+ * @throws IOException
+ * if reftable cannot be written.
+ */
+ public void writeRef(Ref ref) throws IOException {
+ writeRef(ref, maxUpdateIndex);
+ }
+
+ /**
+ * Write one reference to the reftable.
+ * <p>
+ * References must be passed in sorted order.
+ *
+ * @param ref
+ * the reference to store.
+ * @param updateIndex
+ * the updateIndex that modified this reference. Must be
+ * {@code >= minUpdateIndex} for this file.
+ * @throws IOException
+ * if reftable cannot be written.
+ */
+ public void writeRef(Ref ref, long updateIndex) throws IOException {
+ if (updateIndex < minUpdateIndex) {
+ throw new IllegalArgumentException();
+ }
+ long d = updateIndex - minUpdateIndex;
+ long blockPos = refs.write(new RefEntry(ref, d));
+ indexRef(ref, blockPos);
+ }
+
+ private void indexRef(Ref ref, long blockPos) {
+ if (indexObjects && !ref.isSymbolic()) {
+ indexId(ref.getObjectId(), blockPos);
+ indexId(ref.getPeeledObjectId(), blockPos);
+ }
+ }
+
+ private void indexId(ObjectId id, long blockPos) {
+ if (id != null) {
+ RefList l = obj2ref.get(id);
+ if (l == null) {
+ l = new RefList(id);
+ obj2ref.add(l);
+ }
+ l.addBlock(blockPos);
+ }
+ }
+
+ /**
+ * Write one reflog entry to the reftable.
+ * <p>
+ * Reflog entries must be written in reference name and descending
+ * {@code updateIndex} (highest first) order.
+ *
+ * @param ref
+ * name of the reference.
+ * @param updateIndex
+ * identifier of the transaction that created the log record. The
+ * {@code updateIndex} must be unique within the scope of
+ * {@code ref}, and must be within the bounds defined by
+ * {@code minUpdateIndex <= updateIndex <= maxUpdateIndex}.
+ * @param who
+ * committer of the reflog entry.
+ * @param oldId
+ * prior id; pass {@link ObjectId#zeroId()} for creations.
+ * @param newId
+ * new id; pass {@link ObjectId#zeroId()} for deletions.
+ * @param message
+ * optional message (may be null).
+ * @throws IOException
+ * if reftable cannot be written.
+ */
+ public void writeLog(String ref, long updateIndex, PersonIdent who,
+ ObjectId oldId, ObjectId newId, @Nullable String message)
+ throws IOException {
+ String msg = message != null ? message : ""; //$NON-NLS-1$
+ beginLog();
+ logs.write(new LogEntry(ref, updateIndex, who, oldId, newId, msg));
+ }
+
+ /**
+ * Record deletion of one reflog entry in this reftable.
+ *
+ * <p>
+ * The deletion can shadow an entry stored in a lower table in the stack.
+ * This is useful for {@code refs/stash} and dropping an entry from its
+ * reflog.
+ * <p>
+ * Deletion must be properly interleaved in sorted updateIndex order with
+ * any other logs written by
+ * {@link #writeLog(String, long, PersonIdent, ObjectId, ObjectId, String)}.
+ *
+ * @param ref
+ * the ref to delete (hide) a reflog entry from.
+ * @param updateIndex
+ * the update index that must be hidden.
+ * @throws IOException
+ * if reftable cannot be written.
+ */
+ public void deleteLog(String ref, long updateIndex) throws IOException {
+ beginLog();
+ logs.write(new DeleteLogEntry(ref, updateIndex));
+ }
+
+ private void beginLog() throws IOException {
+ if (logs == null) {
+ finishRefAndObjSections(); // close prior ref blocks and their index, if present.
+ out.flushFileHeader();
+ out.setBlockSize(logBlockSize);
+ logs = new Section(LOG_BLOCK_TYPE);
+ }
+ }
+
+ /**
+ * @return an estimate of the current size in bytes of the reftable, if it
+ * was finished right now. Estimate is only accurate if
+ * {@link ReftableConfig#setIndexObjects(boolean)} is {@code false}
+ * and {@link ReftableConfig#setMaxIndexLevels(int)} is {@code 1}.
+ */
+ public long estimateTotalBytes() {
+ long bytes = out.size();
+ if (bytes == 0) {
+ bytes += FILE_HEADER_LEN;
+ }
+ if (cur != null) {
+ long curBlockPos = out.size();
+ int sz = cur.currentSize();
+ bytes += sz;
+
+ IndexBuilder idx = null;
+ if (cur.blockType() == REF_BLOCK_TYPE) {
+ idx = refs.idx;
+ } else if (cur.blockType() == LOG_BLOCK_TYPE) {
+ idx = logs.idx;
+ }
+ if (idx != null && shouldHaveIndex(idx)) {
+ if (idx == refs.idx) {
+ bytes += out.estimatePadBetweenBlocks(sz);
+ }
+ bytes += idx.estimateBytes(curBlockPos);
+ }
+ }
+ bytes += FILE_FOOTER_LEN;
+ return bytes;
+ }
+
+ /**
+ * Finish writing the reftable by writing its trailer.
+ *
+ * @return {@code this}
+ * @throws IOException
+ * if reftable cannot be written.
+ */
+ public ReftableWriter finish() throws IOException {
+ finishRefAndObjSections();
+ finishLogSection();
+ writeFileFooter();
+ out.finishFile();
+
+ stats = new Stats(this, out);
+ out = null;
+ obj2ref = null;
+ cur = null;
+ refs = null;
+ objs = null;
+ logs = null;
+ return this;
+ }
+
+ private void finishRefAndObjSections() throws IOException {
+ if (cur != null && cur.blockType() == REF_BLOCK_TYPE) {
+ refs.finishSectionMaybeWriteIndex();
+ if (indexObjects && !obj2ref.isEmpty() && refs.idx.bytes > 0) {
+ writeObjBlocks();
+ }
+ obj2ref = null;
+ }
+ }
+
+ private void writeObjBlocks() throws IOException {
+ List<RefList> sorted = sortById(obj2ref);
+ obj2ref = null;
+ objIdLen = shortestUniqueAbbreviation(sorted);
+
+ out.padBetweenBlocksToNextBlock();
+ objs = new Section(OBJ_BLOCK_TYPE);
+ objs.entryCnt = sorted.size();
+ for (RefList l : sorted) {
+ objs.write(new ObjEntry(objIdLen, l, l.blockPos));
+ }
+ objs.finishSectionMaybeWriteIndex();
+ }
+
+ private void finishLogSection() throws IOException {
+ if (cur != null && cur.blockType() == LOG_BLOCK_TYPE) {
+ logs.finishSectionMaybeWriteIndex();
+ }
+ }
+
+ private boolean shouldHaveIndex(IndexBuilder idx) {
+ int threshold;
+ if (idx == refs.idx && alignBlocks) {
+ threshold = 4;
+ } else {
+ threshold = 1;
+ }
+ return idx.entries.size() + (cur != null ? 1 : 0) > threshold;
+ }
+
+ private void writeFileHeader() {
+ byte[] hdr = new byte[FILE_HEADER_LEN];
+ encodeHeader(hdr);
+ out.write(hdr, 0, FILE_HEADER_LEN);
+ }
+
+ private void encodeHeader(byte[] hdr) {
+ System.arraycopy(FILE_HEADER_MAGIC, 0, hdr, 0, 4);
+ int bs = alignBlocks ? refBlockSize : 0;
+ NB.encodeInt32(hdr, 4, (VERSION_1 << 24) | bs);
+ NB.encodeInt64(hdr, 8, minUpdateIndex);
+ NB.encodeInt64(hdr, 16, maxUpdateIndex);
+ }
+
+ private void writeFileFooter() {
+ int ftrLen = FILE_FOOTER_LEN;
+ byte[] ftr = new byte[ftrLen];
+ encodeHeader(ftr);
+
+ NB.encodeInt64(ftr, 24, indexPosition(refs));
+ NB.encodeInt64(ftr, 32, (firstBlockPosition(objs) << 5) | objIdLen);
+ NB.encodeInt64(ftr, 40, indexPosition(objs));
+ NB.encodeInt64(ftr, 48, firstBlockPosition(logs));
+ NB.encodeInt64(ftr, 56, indexPosition(logs));
+
+ CRC32 crc = new CRC32();
+ crc.update(ftr, 0, ftrLen - 4);
+ NB.encodeInt32(ftr, ftrLen - 4, (int) crc.getValue());
+
+ out.write(ftr, 0, ftrLen);
+ }
+
+ private static long firstBlockPosition(@Nullable Section s) {
+ return s != null ? s.firstBlockPosition : 0;
+ }
+
+ private static long indexPosition(@Nullable Section s) {
+ return s != null && s.idx != null ? s.idx.rootPosition : 0;
+ }
+
+ /** @return statistics of the last written reftable. */
+ public Stats getStats() {
+ return stats;
+ }
+
+ /** Statistics about a written reftable. */
+ public static class Stats {
+ private final int refBlockSize;
+ private final int logBlockSize;
+ private final int restartInterval;
+
+ private final long minUpdateIndex;
+ private final long maxUpdateIndex;
+
+ private final long refCnt;
+ private final long objCnt;
+ private final int objIdLen;
+ private final long logCnt;
+ private final long refBytes;
+ private final long objBytes;
+ private final long logBytes;
+ private final long paddingUsed;
+ private final long totalBytes;
+
+ private final int refIndexSize;
+ private final int refIndexLevels;
+ private final int objIndexSize;
+ private final int objIndexLevels;
+
+ Stats(ReftableWriter w, ReftableOutputStream o) {
+ refBlockSize = w.refBlockSize;
+ logBlockSize = w.logBlockSize;
+ restartInterval = w.restartInterval;
+
+ minUpdateIndex = w.minUpdateIndex;
+ maxUpdateIndex = w.maxUpdateIndex;
+ paddingUsed = o.paddingUsed();
+ totalBytes = o.size();
+
+ refCnt = w.refs.entryCnt;
+ refBytes = w.refs.bytes;
+
+ objCnt = w.objs != null ? w.objs.entryCnt : 0;
+ objBytes = w.objs != null ? w.objs.bytes : 0;
+ objIdLen = w.objIdLen;
+
+ logCnt = w.logs != null ? w.logs.entryCnt : 0;
+ logBytes = w.logs != null ? w.logs.bytes : 0;
+
+ IndexBuilder refIdx = w.refs.idx;
+ refIndexSize = refIdx.bytes;
+ refIndexLevels = refIdx.levels;
+
+ IndexBuilder objIdx = w.objs != null ? w.objs.idx : null;
+ objIndexSize = objIdx != null ? objIdx.bytes : 0;
+ objIndexLevels = objIdx != null ? objIdx.levels : 0;
+ }
+
+ /** @return number of bytes in a ref block. */
+ public int refBlockSize() {
+ return refBlockSize;
+ }
+
+ /** @return number of bytes to compress into a log block. */
+ public int logBlockSize() {
+ return logBlockSize;
+ }
+
+ /** @return number of references between binary search markers. */
+ public int restartInterval() {
+ return restartInterval;
+ }
+
+ /** @return smallest update index contained in this reftable. */
+ public long minUpdateIndex() {
+ return minUpdateIndex;
+ }
+
+ /** @return largest update index contained in this reftable. */
+ public long maxUpdateIndex() {
+ return maxUpdateIndex;
+ }
+
+ /** @return total number of references in the reftable. */
+ public long refCount() {
+ return refCnt;
+ }
+
+ /** @return number of unique objects in the reftable. */
+ public long objCount() {
+ return objCnt;
+ }
+
+ /** @return total number of log records in the reftable. */
+ public long logCount() {
+ return logCnt;
+ }
+
+ /** @return number of bytes for references, including ref index. */
+ public long refBytes() {
+ return refBytes;
+ }
+
+ /** @return number of bytes for objects, including object index. */
+ public long objBytes() {
+ return objBytes;
+ }
+
+ /** @return number of bytes for log, including log index. */
+ public long logBytes() {
+ return logBytes;
+ }
+
+ /** @return total number of bytes in the reftable. */
+ public long totalBytes() {
+ return totalBytes;
+ }
+
+ /** @return bytes of padding used to maintain block alignment. */
+ public long paddingBytes() {
+ return paddingUsed;
+ }
+
+ /** @return number of bytes in the ref index; 0 if no index was used. */
+ public int refIndexSize() {
+ return refIndexSize;
+ }
+
+ /** @return number of levels in the ref index. */
+ public int refIndexLevels() {
+ return refIndexLevels;
+ }
+
+ /** @return number of bytes in the object index; 0 if no index. */
+ public int objIndexSize() {
+ return objIndexSize;
+ }
+
+ /** @return number of levels in the object index. */
+ public int objIndexLevels() {
+ return objIndexLevels;
+ }
+
+ /**
+ * @return number of bytes required to uniquely identify all objects in
+ * the reftable. Unique abbreviations in hex would be
+ * {@code 2 * objIdLength()}.
+ */
+ public int objIdLength() {
+ return objIdLen;
+ }
+ }
+
+ private static List<RefList> sortById(ObjectIdSubclassMap<RefList> m) {
+ List<RefList> s = new ArrayList<>(m.size());
+ for (RefList l : m) {
+ s.add(l);
+ }
+ Collections.sort(s);
+ return s;
+ }
+
+ private static int shortestUniqueAbbreviation(List<RefList> in) {
+ // Estimate minimum number of bytes necessary for unique abbreviations.
+ int bytes = Math.max(2, (int) (log(in.size()) / log(8)));
+ Set<AbbreviatedObjectId> tmp = new HashSet<>((int) (in.size() * 0.75f));
+ retry: for (;;) {
+ int hexLen = bytes * 2;
+ for (ObjectId id : in) {
+ AbbreviatedObjectId a = id.abbreviate(hexLen);
+ if (!tmp.add(a)) {
+ if (++bytes >= OBJECT_ID_LENGTH) {
+ return OBJECT_ID_LENGTH;
+ }
+ tmp.clear();
+ continue retry;
+ }
+ }
+ return bytes;
+ }
+ }
+
+ private static class RefList extends ObjectIdOwnerMap.Entry {
+ final LongList blockPos = new LongList(2);
+
+ RefList(AnyObjectId id) {
+ super(id);
+ }
+
+ void addBlock(long pos) {
+ if (!blockPos.contains(pos)) {
+ blockPos.add(pos);
+ }
+ }
+ }
+
+ private class Section {
+ final IndexBuilder idx;
+ final long firstBlockPosition;
+
+ long entryCnt;
+ long bytes;
+
+ Section(byte keyType) {
+ idx = new IndexBuilder(keyType);
+ firstBlockPosition = out.size();
+ }
+
+ long write(BlockWriter.Entry entry) throws IOException {
+ if (cur == null) {
+ beginBlock(entry);
+ } else if (!cur.tryAdd(entry)) {
+ flushCurBlock();
+ if (cur.padBetweenBlocks()) {
+ out.padBetweenBlocksToNextBlock();
+ }
+ beginBlock(entry);
+ }
+ entryCnt++;
+ return out.size();
+ }
+
+ private void beginBlock(BlockWriter.Entry entry)
+ throws BlockSizeTooSmallException {
+ byte blockType = entry.blockType();
+ int bs = out.bytesAvailableInBlock();
+ cur = new BlockWriter(blockType, idx.keyType, bs, restartInterval);
+ cur.mustAdd(entry);
+ }
+
+ void flushCurBlock() throws IOException {
+ idx.entries.add(new IndexEntry(cur.lastKey(), out.size()));
+ cur.writeTo(out);
+ }
+
+ void finishSectionMaybeWriteIndex() throws IOException {
+ flushCurBlock();
+ cur = null;
+ if (shouldHaveIndex(idx)) {
+ idx.writeIndex();
+ }
+ bytes = out.size() - firstBlockPosition;
+ }
+ }
+
+ private class IndexBuilder {
+ final byte keyType;
+ List<IndexEntry> entries = new ArrayList<>();
+ long rootPosition;
+ int bytes;
+ int levels;
+
+ IndexBuilder(byte kt) {
+ keyType = kt;
+ }
+
+ int estimateBytes(long curBlockPos) {
+ BlockWriter b = new BlockWriter(
+ INDEX_BLOCK_TYPE, keyType,
+ MAX_BLOCK_SIZE,
+ Math.max(restartInterval, entries.size() / MAX_RESTARTS));
+ try {
+ for (Entry e : entries) {
+ b.mustAdd(e);
+ }
+ if (cur != null) {
+ b.mustAdd(new IndexEntry(cur.lastKey(), curBlockPos));
+ }
+ } catch (BlockSizeTooSmallException e) {
+ return b.currentSize();
+ }
+ return b.currentSize();
+ }
+
+ void writeIndex() throws IOException {
+ if (padBetweenBlocks(keyType)) {
+ out.padBetweenBlocksToNextBlock();
+ }
+ long startPos = out.size();
+ writeMultiLevelIndex(entries);
+ bytes = (int) (out.size() - startPos);
+ entries = null;
+ }
+
+ private void writeMultiLevelIndex(List<IndexEntry> keys)
+ throws IOException {
+ levels = 1;
+ while (maxIndexLevels == 0 || levels < maxIndexLevels) {
+ keys = writeOneLevel(keys);
+ if (keys == null) {
+ return;
+ }
+ levels++;
+ }
+
+ // When maxIndexLevels has restricted the writer, write one
+ // index block with the entire remaining set of keys.
+ BlockWriter b = new BlockWriter(
+ INDEX_BLOCK_TYPE, keyType,
+ MAX_BLOCK_SIZE,
+ Math.max(restartInterval, keys.size() / MAX_RESTARTS));
+ for (Entry e : keys) {
+ b.mustAdd(e);
+ }
+ rootPosition = out.size();
+ b.writeTo(out);
+ }
+
+ private List<IndexEntry> writeOneLevel(List<IndexEntry> keys)
+ throws IOException {
+ Section thisLevel = new Section(keyType);
+ for (Entry e : keys) {
+ thisLevel.write(e);
+ }
+ if (!thisLevel.idx.entries.isEmpty()) {
+ thisLevel.flushCurBlock();
+ if (cur.padBetweenBlocks()) {
+ out.padBetweenBlocksToNextBlock();
+ }
+ cur = null;
+ return thisLevel.idx.entries;
+ }
+
+ // The current block fit entire level; make it the root.
+ rootPosition = out.size();
+ cur.writeTo(out);
+ cur = null;
+ return null;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java
index 29a379e..0567051 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java
@@ -336,7 +336,7 @@ private int mask(final int word, final int v) {
@Override
public int hashCode() {
- return w2;
+ return w1;
}
@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
index de1003b..825c1f7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
@@ -738,4 +738,4 @@ protected FS safeFS() {
protected final B self() {
return (B) this;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
index 3f6995d..bcf9065 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
@@ -58,6 +58,8 @@
import java.util.List;
import java.util.concurrent.TimeoutException;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -80,8 +82,10 @@ public class BatchRefUpdate {
* clock skew between machines on the same LAN using an NTP server also on
* the same LAN should be under 5 seconds. 5 seconds is also not that long
* for a large `git push` operation to complete.
+ *
+ * @since 4.9
*/
- private static final Duration MAX_WAIT = Duration.ofSeconds(5);
+ protected static final Duration MAX_WAIT = Duration.ofSeconds(5);
private final RefDatabase refdb;
@@ -100,6 +104,12 @@ public class BatchRefUpdate {
/** Should the result value be appended to {@link #refLogMessage}. */
private boolean refLogIncludeResult;
+ /**
+ * Should reflogs be written even if the configured default for this ref is
+ * not to write it.
+ */
+ private boolean forceRefLog;
+
/** Push certificate associated with this update. */
private PushCertificate pushCert;
@@ -173,25 +183,42 @@ public BatchRefUpdate setRefLogIdent(final PersonIdent pi) {
* @return message the caller wants to include in the reflog; null if the
* update should not be logged.
*/
+ @Nullable
public String getRefLogMessage() {
return refLogMessage;
}
- /** @return {@code true} if the ref log message should show the result. */
+ /**
+ * Check whether the reflog message should include the result of the update,
+ * such as fast-forward or force-update.
+ * <p>
+ * Describes the default for commands in this batch that do not override it
+ * with {@link ReceiveCommand#setRefLogMessage(String, boolean)}.
+ *
+ * @return true if the message should include the result.
+ */
public boolean isRefLogIncludingResult() {
return refLogIncludeResult;
}
/**
* Set the message to include in the reflog.
+ * <p>
+ * Repository implementations may limit which reflogs are written by default,
+ * based on the project configuration. If a repo is not configured to write
+ * logs for this ref by default, setting the message alone may have no effect.
+ * To indicate that the repo should write logs for this update in spite of
+ * configured defaults, use {@link #setForceRefLog(boolean)}.
+ * <p>
+ * Describes the default for commands in this batch that do not override it
+ * with {@link ReceiveCommand#setRefLogMessage(String, boolean)}.
*
* @param msg
- * the message to describe this change. It may be null if
- * appendStatus is null in order not to append to the reflog
+ * the message to describe this change. If null and appendStatus is
+ * false, the reflog will not be updated.
* @param appendStatus
* true if the status of the ref change (fast-forward or
- * forced-update) should be appended to the user supplied
- * message.
+ * forced-update) should be appended to the user supplied message.
* @return {@code this}.
*/
public BatchRefUpdate setRefLogMessage(String msg, boolean appendStatus) {
@@ -209,6 +236,8 @@ else if (msg == null && appendStatus) {
/**
* Don't record this update in the ref's associated reflog.
+ * <p>
+ * Equivalent to {@code setRefLogMessage(null, false)}.
*
* @return {@code this}.
*/
@@ -218,12 +247,38 @@ public BatchRefUpdate disableRefLog() {
return this;
}
- /** @return true if log has been disabled by {@link #disableRefLog()}. */
+ /**
+ * Force writing a reflog for the updated ref.
+ *
+ * @param force whether to force.
+ * @return {@code this}
+ * @since 4.9
+ */
+ public BatchRefUpdate setForceRefLog(boolean force) {
+ forceRefLog = force;
+ return this;
+ }
+
+ /**
+ * Check whether log has been disabled by {@link #disableRefLog()}.
+ *
+ * @return true if disabled.
+ */
public boolean isRefLogDisabled() {
return refLogMessage == null;
}
/**
+ * Check whether the reflog should be written regardless of repo defaults.
+ *
+ * @return whether force writing is enabled.
+ * @since 4.9
+ */
+ protected boolean isForceRefLog() {
+ return forceRefLog;
+ }
+
+ /**
* Request that all updates in this batch be performed atomically.
* <p>
* When atomic updates are used, either all commands apply successfully, or
@@ -323,14 +378,29 @@ public BatchRefUpdate addCommand(Collection<ReceiveCommand> cmd) {
/**
* Gets the list of option strings associated with this update.
*
- * @return pushOptions
+ * @return push options that were passed to {@link #execute}; prior to calling
+ * {@link #execute}, always returns null.
* @since 4.5
*/
+ @Nullable
public List<String> getPushOptions() {
return pushOptions;
}
/**
+ * Set push options associated with this update.
+ * <p>
+ * Implementations must call this at the top of {@link #execute(RevWalk,
+ * ProgressMonitor, List)}.
+ *
+ * @param options options passed to {@code execute}.
+ * @since 4.9
+ */
+ protected void setPushOptions(List<String> options) {
+ pushOptions = options;
+ }
+
+ /**
* @return list of timestamps the batch must wait for.
* @since 4.6
*/
@@ -396,7 +466,7 @@ public void execute(RevWalk walk, ProgressMonitor monitor,
}
if (options != null) {
- pushOptions = options;
+ setPushOptions(options);
}
monitor.beginTask(JGitText.get().updatingReferences, commands.size());
@@ -407,6 +477,11 @@ public void execute(RevWalk walk, ProgressMonitor monitor,
for (ReceiveCommand cmd : commands) {
try {
if (cmd.getResult() == NOT_ATTEMPTED) {
+ if (isMissing(walk, cmd.getOldId())
+ || isMissing(walk, cmd.getNewId())) {
+ cmd.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT);
+ continue;
+ }
cmd.updateType(walk);
switch (cmd.getType()) {
case CREATE:
@@ -462,7 +537,7 @@ public void execute(RevWalk walk, ProgressMonitor monitor,
break SWITCH;
}
ru.setCheckConflicting(false);
- addRefToPrefixes(takenPrefixes, cmd.getRefName());
+ takenPrefixes.addAll(getPrefixes(cmd.getRefName()));
takenNames.add(cmd.getRefName());
cmd.setResult(ru.update(walk));
}
@@ -478,6 +553,19 @@ public void execute(RevWalk walk, ProgressMonitor monitor,
monitor.endTask();
}
+ private static boolean isMissing(RevWalk walk, ObjectId id)
+ throws IOException {
+ if (id.equals(ObjectId.zeroId())) {
+ return false; // Explicit add or delete is not missing.
+ }
+ try {
+ walk.parseAny(id);
+ return false;
+ } catch (MissingObjectException e) {
+ return true;
+ }
+ }
+
/**
* Wait for timestamps to be in the past, aborting commands on timeout.
*
@@ -523,29 +611,45 @@ public void execute(RevWalk walk, ProgressMonitor monitor)
execute(walk, monitor, null);
}
- private static Collection<String> getTakenPrefixes(
- final Collection<String> names) {
+ private static Collection<String> getTakenPrefixes(Collection<String> names) {
Collection<String> ref = new HashSet<>();
- for (String name : names)
- ref.addAll(getPrefixes(name));
+ for (String name : names) {
+ addPrefixesTo(name, ref);
+ }
return ref;
}
- private static void addRefToPrefixes(Collection<String> prefixes,
- String name) {
- for (String prefix : getPrefixes(name)) {
- prefixes.add(prefix);
- }
+ /**
+ * Get all path prefixes of a ref name.
+ *
+ * @param name
+ * ref name.
+ * @return path prefixes of the ref name. For {@code refs/heads/foo}, returns
+ * {@code refs} and {@code refs/heads}.
+ * @since 4.9
+ */
+ protected static Collection<String> getPrefixes(String name) {
+ Collection<String> ret = new HashSet<>();
+ addPrefixesTo(name, ret);
+ return ret;
}
- static Collection<String> getPrefixes(String s) {
- Collection<String> ret = new HashSet<>();
- int p1 = s.indexOf('/');
+ /**
+ * Add prefixes of a ref name to an existing collection.
+ *
+ * @param name
+ * ref name.
+ * @param out
+ * path prefixes of the ref name. For {@code refs/heads/foo},
+ * returns {@code refs} and {@code refs/heads}.
+ * @since 4.9
+ */
+ protected static void addPrefixesTo(String name, Collection<String> out) {
+ int p1 = name.indexOf('/');
while (p1 > 0) {
- ret.add(s.substring(0, p1));
- p1 = s.indexOf('/', p1 + 1);
+ out.add(name.substring(0, p1));
+ p1 = name.indexOf('/', p1 + 1);
}
- return ret;
}
/**
@@ -560,11 +664,12 @@ static Collection<String> getPrefixes(String s) {
*/
protected RefUpdate newUpdate(ReceiveCommand cmd) throws IOException {
RefUpdate ru = refdb.newUpdate(cmd.getRefName(), false);
- if (isRefLogDisabled())
+ if (isRefLogDisabled(cmd)) {
ru.disableRefLog();
- else {
+ } else {
ru.setRefLogIdent(refLogIdent);
- ru.setRefLogMessage(refLogMessage, refLogIncludeResult);
+ ru.setRefLogMessage(getRefLogMessage(cmd), isRefLogIncludingResult(cmd));
+ ru.setForceRefLog(isForceRefLog(cmd));
}
ru.setPushCertificate(pushCert);
switch (cmd.getType()) {
@@ -585,6 +690,62 @@ protected RefUpdate newUpdate(ReceiveCommand cmd) throws IOException {
}
}
+ /**
+ * Check whether reflog is disabled for a command.
+ *
+ * @param cmd
+ * specific command.
+ * @return whether the reflog is disabled, taking into account the state from
+ * this instance as well as overrides in the given command.
+ * @since 4.9
+ */
+ protected boolean isRefLogDisabled(ReceiveCommand cmd) {
+ return cmd.hasCustomRefLog() ? cmd.isRefLogDisabled() : isRefLogDisabled();
+ }
+
+ /**
+ * Get reflog message for a command.
+ *
+ * @param cmd
+ * specific command.
+ * @return reflog message, taking into account the state from this instance as
+ * well as overrides in the given command.
+ * @since 4.9
+ */
+ protected String getRefLogMessage(ReceiveCommand cmd) {
+ return cmd.hasCustomRefLog() ? cmd.getRefLogMessage() : getRefLogMessage();
+ }
+
+ /**
+ * Check whether the reflog message for a command should include the result.
+ *
+ * @param cmd
+ * specific command.
+ * @return whether the reflog message should show the result, taking into
+ * account the state from this instance as well as overrides in the
+ * given command.
+ * @since 4.9
+ */
+ protected boolean isRefLogIncludingResult(ReceiveCommand cmd) {
+ return cmd.hasCustomRefLog()
+ ? cmd.isRefLogIncludingResult() : isRefLogIncludingResult();
+ }
+
+ /**
+ * Check whether the reflog for a command should be written regardless of repo
+ * defaults.
+ *
+ * @param cmd
+ * specific command.
+ * @return whether force writing is enabled.
+ * @since 4.9
+ */
+ protected boolean isForceRefLog(ReceiveCommand cmd) {
+ Boolean isForceRefLog = cmd.isForceRefLog();
+ return isForceRefLog != null ? isForceRefLog.booleanValue()
+ : isForceRefLog();
+ }
+
@Override
public String toString() {
StringBuilder r = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapObject.java
index 345016c..4e0dc2c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapObject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapObject.java
@@ -62,4 +62,4 @@ public abstract class BitmapObject {
* @return unique hash of this object.
*/
public abstract ObjectId getObjectId();
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java
new file mode 100644
index 0000000..0fe63ae
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobObjectChecker.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+
+/**
+ * Verifies that a blob object is a valid object.
+ * <p>
+ * Unlike trees, commits and tags, there's no validity of blobs. Implementers
+ * can optionally implement this blob checker to reject certain blobs.
+ *
+ * @since 4.9
+ */
+public interface BlobObjectChecker {
+ /** No-op implementation of {@link BlobObjectChecker}. */
+ public static final BlobObjectChecker NULL_CHECKER =
+ new BlobObjectChecker() {
+ @Override
+ public void update(byte[] in, int p, int len) {
+ // Empty implementation.
+ }
+
+ @Override
+ public void endBlob(AnyObjectId id) {
+ // Empty implementation.
+ }
+ };
+
+ /**
+ * Check a new fragment of the blob.
+ *
+ * @param in
+ * input array of bytes.
+ * @param offset
+ * offset to start at from {@code in}.
+ * @param len
+ * length of the fragment to check.
+ */
+ void update(byte[] in, int offset, int len);
+
+ /**
+ * Finalize the blob checking.
+ *
+ * @param id
+ * identity of the object being checked.
+ * @throws CorruptObjectException
+ * if any error was detected.
+ */
+ void endBlob(AnyObjectId id) throws CorruptObjectException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java
index d6608cd..34d0b14 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CheckoutEntry.java
@@ -17,4 +17,4 @@ public interface CheckoutEntry {
*/
public abstract String getToBranch();
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
index f45c71c..b0f5c2c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -51,9 +51,6 @@
package org.eclipse.jgit.lib;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
@@ -62,8 +59,6 @@
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.events.ConfigChangedEvent;
@@ -71,21 +66,24 @@
import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.events.ListenerList;
import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.util.IO;
-import org.eclipse.jgit.util.RawParseUtils;
-import org.eclipse.jgit.util.StringUtils;
-
+import org.eclipse.jgit.transport.RefSpec;
/**
* Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file.
*/
public class Config {
+
private static final String[] EMPTY_STRING_ARRAY = {};
- private static final long KiB = 1024;
- private static final long MiB = 1024 * KiB;
- private static final long GiB = 1024 * MiB;
+
+ static final long KiB = 1024;
+ static final long MiB = 1024 * KiB;
+ static final long GiB = 1024 * MiB;
private static final int MAX_DEPTH = 10;
+ private static final TypedConfigGetter DEFAULT_GETTER = new DefaultTypedConfigGetter();
+
+ private static TypedConfigGetter typedGetter = DEFAULT_GETTER;
+
/** the change listeners */
private final ListenerList listeners = new ListenerList();
@@ -106,7 +104,7 @@ public class Config {
* must ensure it is a special copy of the empty string. It also must
* be treated like the empty string.
*/
- private static final String MAGIC_EMPTY_VALUE = new String();
+ static final String MAGIC_EMPTY_VALUE = new String();
/** Create a configuration with no default fallback. */
public Config() {
@@ -126,6 +124,18 @@ public Config(Config defaultConfig) {
}
/**
+ * Globally sets a {@link TypedConfigGetter} that is subsequently used to
+ * read typed values from all git configs.
+ *
+ * @param getter
+ * to use; if {@code null} use the default getter.
+ * @since 4.9
+ */
+ public static void setTypedConfigGetter(TypedConfigGetter getter) {
+ typedGetter = getter == null ? DEFAULT_GETTER : getter;
+ }
+
+ /**
* Escape the value before saving
*
* @param x
@@ -206,7 +216,7 @@ private static String escapeValue(final String x) {
*/
public int getInt(final String section, final String name,
final int defaultValue) {
- return getInt(section, null, name, defaultValue);
+ return typedGetter.getInt(this, section, null, name, defaultValue);
}
/**
@@ -224,11 +234,8 @@ public int getInt(final String section, final String name,
*/
public int getInt(final String section, String subsection,
final String name, final int defaultValue) {
- final long val = getLong(section, subsection, name, defaultValue);
- if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE)
- return (int) val;
- throw new IllegalArgumentException(MessageFormat.format(JGitText.get().integerValueOutOfRange
- , section, name));
+ return typedGetter.getInt(this, section, subsection, name,
+ defaultValue);
}
/**
@@ -243,7 +250,7 @@ public int getInt(final String section, String subsection,
* @return an integer value from the configuration, or defaultValue.
*/
public long getLong(String section, String name, long defaultValue) {
- return getLong(section, null, name, defaultValue);
+ return typedGetter.getLong(this, section, null, name, defaultValue);
}
/**
@@ -261,37 +268,8 @@ public long getLong(String section, String name, long defaultValue) {
*/
public long getLong(final String section, String subsection,
final String name, final long defaultValue) {
- final String str = getString(section, subsection, name);
- if (str == null)
- return defaultValue;
-
- String n = str.trim();
- if (n.length() == 0)
- return defaultValue;
-
- long mul = 1;
- switch (StringUtils.toLowerCase(n.charAt(n.length() - 1))) {
- case 'g':
- mul = GiB;
- break;
- case 'm':
- mul = MiB;
- break;
- case 'k':
- mul = KiB;
- break;
- }
- if (mul > 1)
- n = n.substring(0, n.length() - 1).trim();
- if (n.length() == 0)
- return defaultValue;
-
- try {
- return mul * Long.parseLong(n);
- } catch (NumberFormatException nfe) {
- throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidIntegerValue
- , section, name, str));
- }
+ return typedGetter.getLong(this, section, subsection, name,
+ defaultValue);
}
/**
@@ -308,7 +286,7 @@ public long getLong(final String section, String subsection,
*/
public boolean getBoolean(final String section, final String name,
final boolean defaultValue) {
- return getBoolean(section, null, name, defaultValue);
+ return typedGetter.getBoolean(this, section, null, name, defaultValue);
}
/**
@@ -327,17 +305,8 @@ public boolean getBoolean(final String section, final String name,
*/
public boolean getBoolean(final String section, String subsection,
final String name, final boolean defaultValue) {
- String n = getRawString(section, subsection, name);
- if (n == null)
- return defaultValue;
- if (MAGIC_EMPTY_VALUE == n)
- return true;
- try {
- return StringUtils.toBoolean(n);
- } catch (IllegalArgumentException err) {
- throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidBooleanValue
- , section, name, n));
- }
+ return typedGetter.getBoolean(this, section, subsection, name,
+ defaultValue);
}
/**
@@ -358,7 +327,8 @@ public boolean getBoolean(final String section, String subsection,
public <T extends Enum<?>> T getEnum(final String section,
final String subsection, final String name, final T defaultValue) {
final T[] all = allValuesOf(defaultValue);
- return getEnum(all, section, subsection, name, defaultValue);
+ return typedGetter.getEnum(this, all, section, subsection, name,
+ defaultValue);
}
@SuppressWarnings("unchecked")
@@ -393,55 +363,8 @@ public <T extends Enum<?>> T getEnum(final String section,
*/
public <T extends Enum<?>> T getEnum(final T[] all, final String section,
final String subsection, final String name, final T defaultValue) {
- String value = getString(section, subsection, name);
- if (value == null)
- return defaultValue;
-
- if (all[0] instanceof ConfigEnum) {
- for (T t : all) {
- if (((ConfigEnum) t).matchConfigValue(value))
- return t;
- }
- }
-
- String n = value.replace(' ', '_');
-
- // Because of c98abc9c0586c73ef7df4172644b7dd21c979e9d being used in
- // the real world before its breakage was fully understood, we must
- // also accept '-' as though it were ' '.
- n = n.replace('-', '_');
-
- T trueState = null;
- T falseState = null;
- for (T e : all) {
- if (StringUtils.equalsIgnoreCase(e.name(), n))
- return e;
- else if (StringUtils.equalsIgnoreCase(e.name(), "TRUE")) //$NON-NLS-1$
- trueState = e;
- else if (StringUtils.equalsIgnoreCase(e.name(), "FALSE")) //$NON-NLS-1$
- falseState = e;
- }
-
- // This is an odd little fallback. C Git sometimes allows boolean
- // values in a tri-state with other things. If we have both a true
- // and a false value in our enumeration, assume its one of those.
- //
- if (trueState != null && falseState != null) {
- try {
- return StringUtils.toBoolean(n) ? trueState : falseState;
- } catch (IllegalArgumentException err) {
- // Fall through and use our custom error below.
- }
- }
-
- if (subsection != null)
- throw new IllegalArgumentException(MessageFormat.format(
- JGitText.get().enumValueNotSupported3, section, subsection,
- name, value));
- else
- throw new IllegalArgumentException(
- MessageFormat.format(JGitText.get().enumValueNotSupported2,
- section, name, value));
+ return typedGetter.getEnum(this, all, section, subsection, name,
+ defaultValue);
}
/**
@@ -515,100 +438,25 @@ public String getString(final String section, String subsection,
*/
public long getTimeUnit(String section, String subsection, String name,
long defaultValue, TimeUnit wantUnit) {
- String valueString = getString(section, subsection, name);
-
- if (valueString == null) {
- return defaultValue;
- }
-
- String s = valueString.trim();
- if (s.length() == 0) {
- return defaultValue;
- }
-
- if (s.startsWith("-")/* negative */) { //$NON-NLS-1$
- throw notTimeUnit(section, subsection, name, valueString);
- }
-
- Matcher m = Pattern.compile("^(0|[1-9][0-9]*)\\s*(.*)$") //$NON-NLS-1$
- .matcher(valueString);
- if (!m.matches()) {
- return defaultValue;
- }
-
- String digits = m.group(1);
- String unitName = m.group(2).trim();
-
- TimeUnit inputUnit;
- int inputMul;
-
- if (unitName.isEmpty()) {
- inputUnit = wantUnit;
- inputMul = 1;
-
- } else if (match(unitName, "ms", "milliseconds")) { //$NON-NLS-1$ //$NON-NLS-2$
- inputUnit = TimeUnit.MILLISECONDS;
- inputMul = 1;
-
- } else if (match(unitName, "s", "sec", "second", "seconds")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
- inputUnit = TimeUnit.SECONDS;
- inputMul = 1;
-
- } else if (match(unitName, "m", "min", "minute", "minutes")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
- inputUnit = TimeUnit.MINUTES;
- inputMul = 1;
-
- } else if (match(unitName, "h", "hr", "hour", "hours")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
- inputUnit = TimeUnit.HOURS;
- inputMul = 1;
-
- } else if (match(unitName, "d", "day", "days")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- inputUnit = TimeUnit.DAYS;
- inputMul = 1;
-
- } else if (match(unitName, "w", "week", "weeks")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- inputUnit = TimeUnit.DAYS;
- inputMul = 7;
-
- } else if (match(unitName, "mon", "month", "months")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- inputUnit = TimeUnit.DAYS;
- inputMul = 30;
-
- } else if (match(unitName, "y", "year", "years")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- inputUnit = TimeUnit.DAYS;
- inputMul = 365;
-
- } else {
- throw notTimeUnit(section, subsection, name, valueString);
- }
-
- try {
- return wantUnit.convert(Long.parseLong(digits) * inputMul,
- inputUnit);
- } catch (NumberFormatException nfe) {
- throw notTimeUnit(section, subsection, unitName, valueString);
- }
+ return typedGetter.getTimeUnit(this, section, subsection, name,
+ defaultValue, wantUnit);
}
- private static boolean match(final String a, final String... cases) {
- for (final String b : cases) {
- if (b != null && b.equalsIgnoreCase(a)) {
- return true;
- }
- }
- return false;
- }
-
- private IllegalArgumentException notTimeUnit(String section,
- String subsection, String name, String valueString) {
- if (subsection != null) {
- return new IllegalArgumentException(
- MessageFormat.format(JGitText.get().invalidTimeUnitValue3,
- section, subsection, name, valueString));
- }
- return new IllegalArgumentException(
- MessageFormat.format(JGitText.get().invalidTimeUnitValue2,
- section, name, valueString));
+ /**
+ * Parse a list of {@link RefSpec}s from the configuration.
+ *
+ * @param section
+ * section the key is in.
+ * @param subsection
+ * subsection the key is in, or null if not in a subsection.
+ * @param name
+ * the key name.
+ * @return a possibly empty list of {@link RefSpec}s
+ * @since 4.9
+ */
+ public List<RefSpec> getRefSpecs(String section, String subsection,
+ String name) {
+ return typedGetter.getRefSpecs(this, section, subsection, name);
}
/**
@@ -757,7 +605,7 @@ protected void fireConfigChangedEvent() {
listeners.dispatch(new ConfigChangedEvent());
}
- private String getRawString(final String section, final String subsection,
+ String getRawString(final String section, final String subsection,
final String name) {
String[] lst = getRawStringList(section, subsection, name);
if (lst != null) {
@@ -1220,10 +1068,6 @@ private List<ConfigLine> fromTextRecurse(final String text, int depth)
e.value = MAGIC_EMPTY_VALUE;
} else
e.value = readValue(in, false, -1);
-
- if (e.section.equals("include")) { //$NON-NLS-1$
- addIncludedConfig(newEntries, e, depth);
- }
} else
throw new ConfigInvalidException(JGitText.get().invalidLineInConfigFile);
}
@@ -1231,36 +1075,6 @@ private List<ConfigLine> fromTextRecurse(final String text, int depth)
return newEntries;
}
- private void addIncludedConfig(final List<ConfigLine> newEntries,
- ConfigLine line, int depth) throws ConfigInvalidException {
- if (!line.name.equals("path") || //$NON-NLS-1$
- line.value == null || line.value.equals(MAGIC_EMPTY_VALUE)) {
- throw new ConfigInvalidException(
- JGitText.get().invalidLineInConfigFile);
- }
- File path = new File(line.value);
- try {
- byte[] bytes = IO.readFully(path);
- String decoded;
- if (isUtf8(bytes)) {
- decoded = RawParseUtils.decode(RawParseUtils.UTF8_CHARSET,
- bytes, 3, bytes.length);
- } else {
- decoded = RawParseUtils.decode(bytes);
- }
- newEntries.addAll(fromTextRecurse(decoded, depth + 1));
- } catch (FileNotFoundException fnfe) {
- if (path.exists()) {
- throw new ConfigInvalidException(MessageFormat
- .format(JGitText.get().cannotReadFile, path), fnfe);
- }
- } catch (IOException ioe) {
- throw new ConfigInvalidException(
- MessageFormat.format(JGitText.get().cannotReadFile, path),
- ioe);
- }
- }
-
private ConfigSnapshot newState() {
return new ConfigSnapshot(Collections.<ConfigLine> emptyList(),
getBaseState());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index 2618180..08c883a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -108,6 +108,12 @@ public class ConfigConstants {
public static final String CONFIG_PULL_SECTION = "pull";
/**
+ * The "merge" section
+ * @since 4.9
+ */
+ public static final String CONFIG_MERGE_SECTION = "merge";
+
+ /**
* The "filter" section
* @since 4.6
*/
@@ -372,6 +378,13 @@ public class ConfigConstants {
public static final String CONFIG_KEY_RENAMES = "renames";
/**
+ * The "inCoreLimit" key in the "merge section". It's a size limit (bytes) used to
+ * control a file to be stored in {@code Heap} or {@code LocalFile} during the merge.
+ * @since 4.9
+ */
+ public static final String CONFIG_KEY_IN_CORE_LIMIT = "inCoreLimit";
+
+ /**
* The "prune" key
* @since 3.3
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
index bda1a27..5bfccda 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2008, Google Inc.
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2012, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2006-2017, Shawn O. Pearce <spearce@spearce.org>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -429,6 +429,20 @@ public final class Constants {
public static final String HOOKS = "hooks";
/**
+ * Merge attribute.
+ *
+ * @since 4.9
+ */
+ public static final String ATTR_MERGE = "merge"; //$NON-NLS-1$
+
+ /**
+ * Binary value for custom merger.
+ *
+ * @since 4.9
+ */
+ public static final String ATTR_BUILTIN_BINARY_MERGER = "binary"; //$NON-NLS-1$
+
+ /**
* Create a new digest function for objects.
*
* @return a new digest object.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
index 40aba63..fdbbe39 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
@@ -57,12 +57,7 @@
*/
public class CoreConfig {
/** Key for {@link Config#get(SectionParser)}. */
- public static final Config.SectionParser<CoreConfig> KEY = new SectionParser<CoreConfig>() {
- @Override
- public CoreConfig parse(final Config cfg) {
- return new CoreConfig(cfg);
- }
- };
+ public static final Config.SectionParser<CoreConfig> KEY = CoreConfig::new;
/** Permissible values for {@code core.autocrlf}. */
public static enum AutoCRLF {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
new file mode 100644
index 0000000..fd37747
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Config.ConfigEnum;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.util.StringUtils;
+
+/**
+ * An {@link TypedConfigGetter} that throws {@link IllegalArgumentException} on
+ * invalid values.
+ *
+ * @since 4.9
+ */
+public class DefaultTypedConfigGetter implements TypedConfigGetter {
+
+ @Override
+ public boolean getBoolean(Config config, String section, String subsection,
+ String name, boolean defaultValue) {
+ String n = config.getRawString(section, subsection, name);
+ if (n == null) {
+ return defaultValue;
+ }
+ if (Config.MAGIC_EMPTY_VALUE == n) {
+ return true;
+ }
+ try {
+ return StringUtils.toBoolean(n);
+ } catch (IllegalArgumentException err) {
+ throw new IllegalArgumentException(MessageFormat.format(
+ JGitText.get().invalidBooleanValue, section, name, n));
+ }
+ }
+
+ @Override
+ public <T extends Enum<?>> T getEnum(Config config, T[] all, String section,
+ String subsection, String name, T defaultValue) {
+ String value = config.getString(section, subsection, name);
+ if (value == null) {
+ return defaultValue;
+ }
+ if (all[0] instanceof ConfigEnum) {
+ for (T t : all) {
+ if (((ConfigEnum) t).matchConfigValue(value)) {
+ return t;
+ }
+ }
+ }
+
+ String n = value.replace(' ', '_');
+
+ // Because of c98abc9c0586c73ef7df4172644b7dd21c979e9d being used in
+ // the real world before its breakage was fully understood, we must
+ // also accept '-' as though it were ' '.
+ n = n.replace('-', '_');
+
+ T trueState = null;
+ T falseState = null;
+ for (T e : all) {
+ if (StringUtils.equalsIgnoreCase(e.name(), n)) {
+ return e;
+ } else if (StringUtils.equalsIgnoreCase(e.name(), "TRUE")) { //$NON-NLS-1$
+ trueState = e;
+ } else if (StringUtils.equalsIgnoreCase(e.name(), "FALSE")) { //$NON-NLS-1$
+ falseState = e;
+ }
+ }
+
+ // This is an odd little fallback. C Git sometimes allows boolean
+ // values in a tri-state with other things. If we have both a true
+ // and a false value in our enumeration, assume its one of those.
+ //
+ if (trueState != null && falseState != null) {
+ try {
+ return StringUtils.toBoolean(n) ? trueState : falseState;
+ } catch (IllegalArgumentException err) {
+ // Fall through and use our custom error below.
+ }
+ }
+
+ if (subsection != null) {
+ throw new IllegalArgumentException(
+ MessageFormat.format(JGitText.get().enumValueNotSupported3,
+ section, subsection, name, value));
+ } else {
+ throw new IllegalArgumentException(
+ MessageFormat.format(JGitText.get().enumValueNotSupported2,
+ section, name, value));
+ }
+ }
+
+ @Override
+ public int getInt(Config config, String section, String subsection,
+ String name, int defaultValue) {
+ long val = config.getLong(section, subsection, name, defaultValue);
+ if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE) {
+ return (int) val;
+ }
+ throw new IllegalArgumentException(MessageFormat
+ .format(JGitText.get().integerValueOutOfRange, section, name));
+ }
+
+ @Override
+ public long getLong(Config config, String section, String subsection,
+ String name, long defaultValue) {
+ final String str = config.getString(section, subsection, name);
+ if (str == null) {
+ return defaultValue;
+ }
+ String n = str.trim();
+ if (n.length() == 0) {
+ return defaultValue;
+ }
+ long mul = 1;
+ switch (StringUtils.toLowerCase(n.charAt(n.length() - 1))) {
+ case 'g':
+ mul = Config.GiB;
+ break;
+ case 'm':
+ mul = Config.MiB;
+ break;
+ case 'k':
+ mul = Config.KiB;
+ break;
+ }
+ if (mul > 1) {
+ n = n.substring(0, n.length() - 1).trim();
+ }
+ if (n.length() == 0) {
+ return defaultValue;
+ }
+ try {
+ return mul * Long.parseLong(n);
+ } catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException(MessageFormat.format(
+ JGitText.get().invalidIntegerValue, section, name, str));
+ }
+ }
+
+ @Override
+ public long getTimeUnit(Config config, String section, String subsection,
+ String name, long defaultValue, TimeUnit wantUnit) {
+ String valueString = config.getString(section, subsection, name);
+
+ if (valueString == null) {
+ return defaultValue;
+ }
+
+ String s = valueString.trim();
+ if (s.length() == 0) {
+ return defaultValue;
+ }
+
+ if (s.startsWith("-")/* negative */) { //$NON-NLS-1$
+ throw notTimeUnit(section, subsection, name, valueString);
+ }
+
+ Matcher m = Pattern.compile("^(0|[1-9][0-9]*)\\s*(.*)$") //$NON-NLS-1$
+ .matcher(valueString);
+ if (!m.matches()) {
+ return defaultValue;
+ }
+
+ String digits = m.group(1);
+ String unitName = m.group(2).trim();
+
+ TimeUnit inputUnit;
+ int inputMul;
+
+ if (unitName.isEmpty()) {
+ inputUnit = wantUnit;
+ inputMul = 1;
+
+ } else if (match(unitName, "ms", "milliseconds")) { //$NON-NLS-1$ //$NON-NLS-2$
+ inputUnit = TimeUnit.MILLISECONDS;
+ inputMul = 1;
+
+ } else if (match(unitName, "s", "sec", "second", "seconds")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ inputUnit = TimeUnit.SECONDS;
+ inputMul = 1;
+
+ } else if (match(unitName, "m", "min", "minute", "minutes")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ inputUnit = TimeUnit.MINUTES;
+ inputMul = 1;
+
+ } else if (match(unitName, "h", "hr", "hour", "hours")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ inputUnit = TimeUnit.HOURS;
+ inputMul = 1;
+
+ } else if (match(unitName, "d", "day", "days")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ inputUnit = TimeUnit.DAYS;
+ inputMul = 1;
+
+ } else if (match(unitName, "w", "week", "weeks")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ inputUnit = TimeUnit.DAYS;
+ inputMul = 7;
+
+ } else if (match(unitName, "mon", "month", "months")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ inputUnit = TimeUnit.DAYS;
+ inputMul = 30;
+
+ } else if (match(unitName, "y", "year", "years")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ inputUnit = TimeUnit.DAYS;
+ inputMul = 365;
+
+ } else {
+ throw notTimeUnit(section, subsection, name, valueString);
+ }
+
+ try {
+ return wantUnit.convert(Long.parseLong(digits) * inputMul,
+ inputUnit);
+ } catch (NumberFormatException nfe) {
+ throw notTimeUnit(section, subsection, unitName, valueString);
+ }
+ }
+
+ private static boolean match(final String a, final String... cases) {
+ for (final String b : cases) {
+ if (b != null && b.equalsIgnoreCase(a)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static IllegalArgumentException notTimeUnit(String section,
+ String subsection, String name, String valueString) {
+ if (subsection != null) {
+ return new IllegalArgumentException(
+ MessageFormat.format(JGitText.get().invalidTimeUnitValue3,
+ section, subsection, name, valueString));
+ }
+ return new IllegalArgumentException(
+ MessageFormat.format(JGitText.get().invalidTimeUnitValue2,
+ section, name, valueString));
+ }
+
+ @Override
+ public @NonNull List<RefSpec> getRefSpecs(Config config, String section,
+ String subsection, String name) {
+ String[] values = config.getStringList(section, subsection, name);
+ List<RefSpec> result = new ArrayList<>(values.length);
+ for (String spec : values) {
+ result.add(new RefSpec(spec));
+ }
+ return result;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java
index a489461..edbc709 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java
@@ -83,7 +83,6 @@ public abstract class FileMode {
public static final int TYPE_MISSING = 0000000;
/** Mode indicating an entry is a tree (aka directory). */
- @SuppressWarnings("synthetic-access")
public static final FileMode TREE = new FileMode(TYPE_TREE,
Constants.OBJ_TREE) {
@Override
@@ -93,7 +92,6 @@ public boolean equals(final int modeBits) {
};
/** Mode indicating an entry is a symbolic link. */
- @SuppressWarnings("synthetic-access")
public static final FileMode SYMLINK = new FileMode(TYPE_SYMLINK,
Constants.OBJ_BLOB) {
@Override
@@ -103,7 +101,6 @@ public boolean equals(final int modeBits) {
};
/** Mode indicating an entry is a non-executable file. */
- @SuppressWarnings("synthetic-access")
public static final FileMode REGULAR_FILE = new FileMode(0100644,
Constants.OBJ_BLOB) {
@Override
@@ -113,7 +110,6 @@ public boolean equals(final int modeBits) {
};
/** Mode indicating an entry is an executable file. */
- @SuppressWarnings("synthetic-access")
public static final FileMode EXECUTABLE_FILE = new FileMode(0100755,
Constants.OBJ_BLOB) {
@Override
@@ -123,7 +119,6 @@ public boolean equals(final int modeBits) {
};
/** Mode indicating an entry is a submodule commit in another repository. */
- @SuppressWarnings("synthetic-access")
public static final FileMode GITLINK = new FileMode(TYPE_GITLINK,
Constants.OBJ_COMMIT) {
@Override
@@ -133,7 +128,6 @@ public boolean equals(final int modeBits) {
};
/** Mode indicating an entry is missing during parallel walks. */
- @SuppressWarnings("synthetic-access")
public static final FileMode MISSING = new FileMode(TYPE_MISSING,
Constants.OBJ_BAD) {
@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
index e544b72..ea573a4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -513,14 +513,10 @@ public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
}
}
- for (int i = 0; i < treeWalk.getTreeCount(); i++) {
- Set<String> values = fileModes.get(treeWalk.getFileMode(i));
- String path = treeWalk.getPathString();
- if (path != null) {
- if (values == null)
- values = new HashSet<>();
- values.add(path);
- fileModes.put(treeWalk.getFileMode(i), values);
+ String path = treeWalk.getPathString();
+ if (path != null) {
+ for (int i = 0; i < treeWalk.getTreeCount(); i++) {
+ recordFileMode(path, treeWalk.getFileMode(i));
}
}
}
@@ -545,19 +541,21 @@ public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
}
Repository subRepo = smw.getRepository();
if (subRepo != null) {
+ String subRepoPath = smw.getPath();
try {
ObjectId subHead = subRepo.resolve("HEAD"); //$NON-NLS-1$
if (subHead != null
- && !subHead.equals(smw.getObjectId()))
- modified.add(smw.getPath());
- else if (ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) {
+ && !subHead.equals(smw.getObjectId())) {
+ modified.add(subRepoPath);
+ recordFileMode(subRepoPath, FileMode.GITLINK);
+ } else if (ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) {
IndexDiff smid = submoduleIndexDiffs.get(smw
.getPath());
if (smid == null) {
smid = new IndexDiff(subRepo,
smw.getObjectId(),
wTreeIt.getWorkingTreeIterator(subRepo));
- submoduleIndexDiffs.put(smw.getPath(), smid);
+ submoduleIndexDiffs.put(subRepoPath, smid);
}
if (smid.diff()) {
if (ignoreSubmoduleMode == IgnoreSubmoduleMode.UNTRACKED
@@ -569,7 +567,8 @@ else if (ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) {
&& smid.getRemoved().isEmpty()) {
continue;
}
- modified.add(smw.getPath());
+ modified.add(subRepoPath);
+ recordFileMode(subRepoPath, FileMode.GITLINK);
}
}
} finally {
@@ -593,6 +592,17 @@ else if (ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) {
return true;
}
+ private void recordFileMode(String path, FileMode mode) {
+ Set<String> values = fileModes.get(mode);
+ if (path != null) {
+ if (values == null) {
+ values = new HashSet<>();
+ fileModes.put(mode, values);
+ }
+ values.add(path);
+ }
+ }
+
private boolean isEntryGitLink(AbstractTreeIterator ti) {
return ((ti != null) && (ti.getEntryRawMode() == FileMode.GITLINK
.getBits()));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
index d8ddcd5..6cc2afa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
@@ -367,7 +367,13 @@ public void check(@Nullable AnyObjectId id, int objType, byte[] raw)
checkTree(id, raw);
break;
case OBJ_BLOB:
- checkBlob(raw);
+ BlobObjectChecker checker = newBlobObjectChecker();
+ if (checker == null) {
+ checkBlob(raw);
+ } else {
+ checker.update(raw, 0, raw.length);
+ checker.endBlob(id);
+ }
break;
default:
report(UNKNOWN_TYPE, id, MessageFormat.format(
@@ -1192,8 +1198,22 @@ private static boolean isPositiveDigit(byte b) {
}
/**
+ * Create a new {@link BlobObjectChecker}.
+ *
+ * @return new BlobObjectChecker or null if it's not provided.
+ * @since 4.9
+ */
+ @Nullable
+ public BlobObjectChecker newBlobObjectChecker() {
+ return null;
+ }
+
+ /**
* Check a blob for errors.
*
+ * <p>This may not be called from PackParser in some cases. Use {@link
+ * #newBlobObjectChecker} instead.
+ *
* @param raw
* the blob data. The array is never modified.
* @throws CorruptObjectException
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
index 857ec9b..b2ffbe6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
@@ -423,6 +423,13 @@ public abstract ObjectId insert(int objectType, long length, InputStream in)
* <p>
* The returned reader should return this inserter instance from {@link
* ObjectReader#getCreatedFromInserter()}.
+ * <p>
+ * Behavior is undefined if an insert method is called on the inserter in the
+ * middle of reading from an {@link ObjectStream} opened from this reader. For
+ * example, reading the remainder of the object may fail, or newly written
+ * data may even be corrupted. Interleaving whole object reads (including
+ * streaming reads) with inserts is fine, just not interleaving streaming
+ * <em>partial</em> object reads with inserts.
*
* @since 3.5
* @return reader for any object, including an object recently inserted by
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
index fc334f0..766b21d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
@@ -58,7 +58,13 @@
* Creates, updates or deletes any reference.
*/
public abstract class RefUpdate {
- /** Status of an update request. */
+ /**
+ * Status of an update request.
+ * <p>
+ * New values may be added to this enum in the future. Callers may assume that
+ * unknown values are failures, and may generally treat them the same as
+ * {@link #REJECTED_OTHER_REASON}.
+ */
public static enum Result {
/** The ref update/delete has not been attempted by the caller. */
NOT_ATTEMPTED,
@@ -114,6 +120,10 @@ public static enum Result {
* merged into the new value. The configuration did not allow a forced
* update/delete to take place, so ref still contains the old value. No
* previous history was lost.
+ * <p>
+ * <em>Note:</em> Despite the general name, this result only refers to the
+ * non-fast-forward case. For more general errors, see {@link
+ * #REJECTED_OTHER_REASON}.
*/
REJECTED,
@@ -139,7 +149,25 @@ public static enum Result {
* The ref was renamed from another name
* <p>
*/
- RENAMED
+ RENAMED,
+
+ /**
+ * One or more objects aren't in the repository.
+ * <p>
+ * This is severe indication of either repository corruption on the
+ * server side, or a bug in the client wherein the client did not supply
+ * all required objects during the pack transfer.
+ *
+ * @since 4.9
+ */
+ REJECTED_MISSING_OBJECT,
+
+ /**
+ * Rejected for some other reason not covered by another enum value.
+ *
+ * @since 4.9
+ */
+ REJECTED_OTHER_REASON;
}
/** New value the caller wants this ref to have. */
@@ -157,6 +185,12 @@ public static enum Result {
/** Should the Result value be appended to {@link #refLogMessage}. */
private boolean refLogIncludeResult;
+ /**
+ * Should reflogs be written even if the configured default for this ref is
+ * not to write it.
+ */
+ private boolean forceRefLog;
+
/** Old value of the ref, obtained after we lock it. */
private ObjectId oldValue;
@@ -278,6 +312,16 @@ public void setDetachingSymbolicRef() {
}
/**
+ * Return whether this update is actually detaching a symbolic ref.
+ *
+ * @return true if detaching a symref.
+ * @since 4.9
+ */
+ public boolean isDetachingSymbolicRef() {
+ return detachingSymbolicRef;
+ }
+
+ /**
* Set the new value the ref will update to.
*
* @param id
@@ -365,6 +409,12 @@ protected boolean isRefLogIncludingResult() {
/**
* Set the message to include in the reflog.
+ * <p>
+ * Repository implementations may limit which reflogs are written by default,
+ * based on the project configuration. If a repo is not configured to write
+ * logs for this ref by default, setting the message alone may have no effect.
+ * To indicate that the repo should write logs for this update in spite of
+ * configured defaults, use {@link #setForceRefLog(boolean)}.
*
* @param msg
* the message to describe this change. It may be null if
@@ -393,6 +443,26 @@ public void disableRefLog() {
}
/**
+ * Force writing a reflog for the updated ref.
+ *
+ * @param force whether to force.
+ * @since 4.9
+ */
+ public void setForceRefLog(boolean force) {
+ forceRefLog = force;
+ }
+
+ /**
+ * Check whether the reflog should be written regardless of repo defaults.
+ *
+ * @return whether force writing is enabled.
+ * @since 4.9
+ */
+ protected boolean isForceRefLog() {
+ return forceRefLog;
+ }
+
+ /**
* The old value of the ref, prior to the update being attempted.
* <p>
* This value may differ before and after the update method. Initially it is
@@ -627,34 +697,47 @@ private Result updateImpl(final RevWalk walk, final Store store)
RevObject oldObj;
// don't make expensive conflict check if this is an existing Ref
- if (oldValue == null && checkConflicting && getRefDatabase().isNameConflicting(getName()))
+ if (oldValue == null && checkConflicting
+ && getRefDatabase().isNameConflicting(getName())) {
return Result.LOCK_FAILURE;
+ }
try {
// If we're detaching a symbolic reference, we should update the reference
// itself. Otherwise, we will update the leaf reference, which should be
// an ObjectIdRef.
- if (!tryLock(!detachingSymbolicRef))
+ if (!tryLock(!detachingSymbolicRef)) {
return Result.LOCK_FAILURE;
+ }
if (expValue != null) {
final ObjectId o;
o = oldValue != null ? oldValue : ObjectId.zeroId();
- if (!AnyObjectId.equals(expValue, o))
+ if (!AnyObjectId.equals(expValue, o)) {
return Result.LOCK_FAILURE;
+ }
}
- if (oldValue == null)
+ try {
+ newObj = safeParseNew(walk, newValue);
+ } catch (MissingObjectException e) {
+ return Result.REJECTED_MISSING_OBJECT;
+ }
+
+ if (oldValue == null) {
return store.execute(Result.NEW);
+ }
- newObj = safeParse(walk, newValue);
- oldObj = safeParse(walk, oldValue);
- if (newObj == oldObj && !detachingSymbolicRef)
+ oldObj = safeParseOld(walk, oldValue);
+ if (newObj == oldObj && !detachingSymbolicRef) {
return store.execute(Result.NO_CHANGE);
+ }
- if (isForceUpdate())
+ if (isForceUpdate()) {
return store.execute(Result.FORCED);
+ }
if (newObj instanceof RevCommit && oldObj instanceof RevCommit) {
- if (walk.isMergedInto((RevCommit) oldObj, (RevCommit) newObj))
+ if (walk.isMergedInto((RevCommit) oldObj, (RevCommit) newObj)) {
return store.execute(Result.FAST_FORWARD);
+ }
}
return Result.REJECTED;
@@ -674,16 +757,23 @@ public void setCheckConflicting(boolean check) {
checkConflicting = check;
}
- private static RevObject safeParse(final RevWalk rw, final AnyObjectId id)
+ private static RevObject safeParseNew(RevWalk rw, AnyObjectId newId)
+ throws IOException {
+ if (newId == null || ObjectId.zeroId().equals(newId)) {
+ return null;
+ }
+ return rw.parseAny(newId);
+ }
+
+ private static RevObject safeParseOld(RevWalk rw, AnyObjectId oldId)
throws IOException {
try {
- return id != null ? rw.parseAny(id) : null;
+ return oldId != null ? rw.parseAny(oldId) : null;
} catch (MissingObjectException e) {
- // We can expect some objects to be missing, like if we are
- // trying to force a deletion of a branch and the object it
- // points to has been pruned from the database due to freak
- // corruption accidents (it happens with 'git new-work-dir').
- //
+ // We can expect some old objects to be missing, like if we are trying to
+ // force a deletion of a branch and the object it points to has been
+ // pruned from the database due to freak corruption accidents (it happens
+ // with 'git new-work-dir').
return null;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java
index 0504646..afa6521 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogEntry.java
@@ -50,6 +50,39 @@
public interface ReflogEntry {
/**
+ * Prefix used in reflog messages when the ref was first created.
+ * <p>
+ * Does not have a corresponding constant in C git, but is untranslated like
+ * the other constants.
+ *
+ * @since 4.9
+ */
+ public static final String PREFIX_CREATED = "created"; //$NON-NLS-1$
+
+ /**
+ * Prefix used in reflog messages when the ref was updated with a fast
+ * forward.
+ * <p>
+ * Untranslated, and exactly matches the
+ * <a href="https://git.kernel.org/pub/scm/git/git.git/tree/builtin/fetch.c?id=f3da2b79be9565779e4f76dc5812c68e156afdf0#n680">
+ * untranslated string in C git</a>.
+ *
+ * @since 4.9
+ */
+ public static final String PREFIX_FAST_FORWARD = "fast-forward"; //$NON-NLS-1$
+
+ /**
+ * Prefix used in reflog messages when the ref was force updated.
+ * <p>
+ * Untranslated, and exactly matches the
+ * <a href="https://git.kernel.org/pub/scm/git/git.git/tree/builtin/fetch.c?id=f3da2b79be9565779e4f76dc5812c68e156afdf0#n695">
+ * untranslated string in C git</a>.
+ *
+ * @since 4.9
+ */
+ public static final String PREFIX_FORCED_UPDATE = "forced-update"; //$NON-NLS-1$
+
+ /**
* @return the commit id before the change
*/
public abstract ObjectId getOldId();
@@ -75,4 +108,4 @@ public interface ReflogEntry {
*/
public abstract CheckoutEntry parseCheckout();
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
index fdab883..d3f2536 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
@@ -86,4 +86,4 @@ public interface ReflogReader {
public abstract List<ReflogEntry> getReverseEntries(int max)
throws IOException;
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index bd23ab9..fdf5966 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -108,7 +108,13 @@
* A repository holds all objects and refs used for managing source code (could
* be any type of file, but source code is what SCM's are typically used for).
* <p>
- * This class is thread-safe.
+ * The thread-safety of a {@link Repository} very much depends on the concrete
+ * implementation. Applications working with a generic {@code Repository} type
+ * must not assume the instance is thread-safe.
+ * <ul>
+ * <li>{@code FileRepository} is thread-safe.
+ * <li>{@code DfsRepository} thread-safety is determined by its subclass.
+ * </ul>
*/
public abstract class Repository implements AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(Repository.class);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java
index e989caf..95be2d1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilder.java
@@ -55,7 +55,7 @@
* <p>
* Single repository applications trying to be compatible with other Git
* implementations are encouraged to use a model such as:
- *
+ *
* <pre>
* new RepositoryBuilder() //
* .setGitDir(gitDirArgument) // --git-dir if supplied, no-op if null
@@ -63,7 +63,7 @@
* .findGitDir() // scan up the file system tree
* .build()
* </pre>
- *
+ *
* @see org.eclipse.jgit.storage.file.FileRepositoryBuilder
*/
public class RepositoryBuilder extends
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java
index 12f7b82..1267506 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java
@@ -43,8 +43,6 @@
package org.eclipse.jgit.lib;
-import java.util.Locale;
-
import org.eclipse.jgit.util.StringUtils;
/**
@@ -79,7 +77,7 @@ private FetchRecurseSubmodulesMode(String configValue) {
@Override
public String toConfigValue() {
- return name().toLowerCase(Locale.ROOT).replace('_', '-');
+ return configValue;
}
@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java
new file mode 100644
index 0000000..594edef
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.lib;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.transport.RefSpec;
+
+/**
+ * Something that knows how to convert plain strings from a git {@link Config}
+ * to typed values.
+ *
+ * @since 4.9
+ */
+public interface TypedConfigGetter {
+
+ /**
+ * Get a boolean value from a git {@link Config}.
+ *
+ * @param config
+ * to get the value from
+ * @param section
+ * section the key is grouped within.
+ * @param subsection
+ * subsection name, such a remote or branch name.
+ * @param name
+ * name of the key to get.
+ * @param defaultValue
+ * default value to return if no value was present.
+ * @return true if any value or defaultValue is true, false for missing or
+ * explicit false
+ */
+ boolean getBoolean(Config config, String section, String subsection,
+ String name, boolean defaultValue);
+
+ /**
+ * Parse an enumeration from a git {@link Config}.
+ *
+ * @param <T>
+ * type of the enumeration object.
+ * @param config
+ * to get the value from
+ * @param all
+ * all possible values in the enumeration which should be
+ * recognized. Typically {@code EnumType.values()}.
+ * @param section
+ * section the key is grouped within.
+ * @param subsection
+ * subsection name, such a remote or branch name.
+ * @param name
+ * name of the key to get.
+ * @param defaultValue
+ * default value to return if no value was present.
+ * @return the selected enumeration value, or {@code defaultValue}.
+ */
+ <T extends Enum<?>> T getEnum(Config config, T[] all, String section,
+ String subsection, String name, T defaultValue);
+
+ /**
+ * Obtain an integer value from a git {@link Config}.
+ *
+ * @param config
+ * to get the value from
+ * @param section
+ * section the key is grouped within.
+ * @param subsection
+ * subsection name, such a remote or branch name.
+ * @param name
+ * name of the key to get.
+ * @param defaultValue
+ * default value to return if no value was present.
+ * @return an integer value from the configuration, or defaultValue.
+ */
+ int getInt(Config config, String section, String subsection, String name,
+ int defaultValue);
+
+ /**
+ * Obtain a long value from a git {@link Config}.
+ *
+ * @param config
+ * to get the value from
+ * @param section
+ * section the key is grouped within.
+ * @param subsection
+ * subsection name, such a remote or branch name.
+ * @param name
+ * name of the key to get.
+ * @param defaultValue
+ * default value to return if no value was present.
+ * @return a long value from the configuration, or defaultValue.
+ */
+ long getLong(Config config, String section, String subsection, String name,
+ long defaultValue);
+
+ /**
+ * Parse a numerical time unit, such as "1 minute", from a git
+ * {@link Config}.
+ *
+ * @param config
+ * to get the value from
+ * @param section
+ * section the key is in.
+ * @param subsection
+ * subsection the key is in, or null if not in a subsection.
+ * @param name
+ * the key name.
+ * @param defaultValue
+ * default value to return if no value was present.
+ * @param wantUnit
+ * the units of {@code defaultValue} and the return value, as
+ * well as the units to assume if the value does not contain an
+ * indication of the units.
+ * @return the value, or {@code defaultValue} if not set, expressed in
+ * {@code units}.
+ */
+ long getTimeUnit(Config config, String section, String subsection,
+ String name, long defaultValue, TimeUnit wantUnit);
+
+
+ /**
+ * Parse a list of {@link RefSpec}s from a git {@link Config}.
+ *
+ * @param config
+ * to get the list from
+ * @param section
+ * section the key is in.
+ * @param subsection
+ * subsection the key is in, or null if not in a subsection.
+ * @param name
+ * the key name.
+ * @return a possibly empty list of {@link RefSpec}s
+ */
+ @NonNull
+ List<RefSpec> getRefSpecs(Config config, String section, String subsection,
+ String name);
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java
index bd393dd..102a451 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java
@@ -51,12 +51,7 @@
/** The standard "user" configuration parameters. */
public class UserConfig {
/** Key for {@link Config#get(SectionParser)}. */
- public static final Config.SectionParser<UserConfig> KEY = new SectionParser<UserConfig>() {
- @Override
- public UserConfig parse(final Config cfg) {
- return new UserConfig(cfg);
- }
- };
+ public static final Config.SectionParser<UserConfig> KEY = UserConfig::new;
private String authorName;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
index 0345921..060f068 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
@@ -143,4 +143,4 @@ private void writeLine(RawText seq, int i) throws IOException {
if (out.isBeginln())
out.write('\n');
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
index 86003e9..246121b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -2,6 +2,7 @@
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>,
* Copyright (C) 2010-2012, Matthias Sohn <matthias.sohn@sap.com>
* Copyright (C) 2012, Research In Motion Limited
+ * Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr)
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -67,6 +68,7 @@
import java.util.List;
import java.util.Map;
+import org.eclipse.jgit.attributes.Attributes;
import org.eclipse.jgit.diff.DiffAlgorithm;
import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm;
import org.eclipse.jgit.diff.RawText;
@@ -83,6 +85,7 @@
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
@@ -270,6 +273,12 @@ public enum MergeFailureReason {
*/
protected MergeAlgorithm mergeAlgorithm;
+ /**
+ * The size limit (bytes) which controls a file to be stored in {@code Heap} or
+ * {@code LocalFile} during the merge.
+ */
+ private int inCoreLimit;
+
private static MergeAlgorithm getMergeAlgorithm(Config config) {
SupportedAlgorithm diffAlg = config.getEnum(
CONFIG_DIFF_SECTION, null, CONFIG_KEY_ALGORITHM,
@@ -277,6 +286,11 @@ private static MergeAlgorithm getMergeAlgorithm(Config config) {
return new MergeAlgorithm(DiffAlgorithm.getAlgorithm(diffAlg));
}
+ private static int getInCoreLimit(Config config) {
+ return config.getInt(
+ ConfigConstants.CONFIG_MERGE_SECTION, ConfigConstants.CONFIG_KEY_IN_CORE_LIMIT, 10 << 20);
+ }
+
private static String[] defaultCommitNames() {
return new String[] { "BASE", "OURS", "THEIRS" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
@@ -287,7 +301,9 @@ private static MergeAlgorithm getMergeAlgorithm(Config config) {
*/
protected ResolveMerger(Repository local, boolean inCore) {
super(local);
- mergeAlgorithm = getMergeAlgorithm(local.getConfig());
+ Config config = local.getConfig();
+ mergeAlgorithm = getMergeAlgorithm(config);
+ inCoreLimit = getInCoreLimit(config);
commitNames = defaultCommitNames();
this.inCore = inCore;
@@ -429,9 +445,10 @@ private DirCacheEntry keep(DirCacheEntry e) {
}
/**
- * Processes one path and tries to merge. This method will do all do all
- * trivial (not content) merges and will also detect if a merge will fail.
- * The merge will fail when one of the following is true
+ * Processes one path and tries to merge taking git attributes in account.
+ * This method will do all trivial (not content) merges and will also detect
+ * if a merge will fail. The merge will fail when one of the following is
+ * true
* <ul>
* <li>the index entry does not match the entry in ours. When merging one
* branch into the current HEAD, ours will point to HEAD and theirs will
@@ -471,11 +488,69 @@ private DirCacheEntry keep(DirCacheEntry e) {
* @throws CorruptObjectException
* @throws IOException
* @since 3.5
+ * @deprecated
+ */
+ @Deprecated
+ protected boolean processEntry(CanonicalTreeParser base,
+ CanonicalTreeParser ours, CanonicalTreeParser theirs,
+ DirCacheBuildIterator index, WorkingTreeIterator work,
+ boolean ignoreConflicts) throws MissingObjectException,
+ IncorrectObjectTypeException, CorruptObjectException, IOException {
+ return processEntry(base, ours, theirs, index, work, ignoreConflicts,
+ null);
+ }
+
+ /**
+ * Processes one path and tries to merge taking git attributes in account.
+ * This method will do all trivial (not content) merges and will also detect
+ * if a merge will fail. The merge will fail when one of the following is
+ * true
+ * <ul>
+ * <li>the index entry does not match the entry in ours. When merging one
+ * branch into the current HEAD, ours will point to HEAD and theirs will
+ * point to the other branch. It is assumed that the index matches the HEAD
+ * because it will only not match HEAD if it was populated before the merge
+ * operation. But the merge commit should not accidentally contain
+ * modifications done before the merge. Check the <a href=
+ * "http://www.kernel.org/pub/software/scm/git/docs/git-read-tree.html#_3_way_merge"
+ * >git read-tree</a> documentation for further explanations.</li>
+ * <li>A conflict was detected and the working-tree file is dirty. When a
+ * conflict is detected the content-merge algorithm will try to write a
+ * merged version into the working-tree. If the file is dirty we would
+ * override unsaved data.</li>
+ * </ul>
+ *
+ * @param base
+ * the common base for ours and theirs
+ * @param ours
+ * the ours side of the merge. When merging a branch into the
+ * HEAD ours will point to HEAD
+ * @param theirs
+ * the theirs side of the merge. When merging a branch into the
+ * current HEAD theirs will point to the branch which is merged
+ * into HEAD.
+ * @param index
+ * the index entry
+ * @param work
+ * the file in the working tree
+ * @param ignoreConflicts
+ * see
+ * {@link ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)}
+ * @param attributes
+ * the attributes defined for this entry
+ * @return <code>false</code> if the merge will fail because the index entry
+ * didn't match ours or the working-dir file was dirty and a
+ * conflict occurred
+ * @throws MissingObjectException
+ * @throws IncorrectObjectTypeException
+ * @throws CorruptObjectException
+ * @throws IOException
+ * @since 4.9
*/
protected boolean processEntry(CanonicalTreeParser base,
CanonicalTreeParser ours, CanonicalTreeParser theirs,
DirCacheBuildIterator index, WorkingTreeIterator work,
- boolean ignoreConflicts)
+ boolean ignoreConflicts, Attributes attributes)
throws MissingObjectException, IncorrectObjectTypeException,
CorruptObjectException, IOException {
enterSubtree = true;
@@ -627,7 +702,8 @@ protected boolean processEntry(CanonicalTreeParser base,
return false;
// Don't attempt to resolve submodule link conflicts
- if (isGitLink(modeO) || isGitLink(modeT)) {
+ if (isGitLink(modeO) || isGitLink(modeT)
+ || !attributes.canBeContentMerged()) {
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);
@@ -636,8 +712,9 @@ protected boolean processEntry(CanonicalTreeParser base,
}
MergeResult<RawText> result = contentMerge(base, ours, theirs);
- if (ignoreConflicts)
+ if (ignoreConflicts) {
result.setContainsConflicts(false);
+ }
updateIndex(base, ours, theirs, result);
if (result.containsConflicts() && !ignoreConflicts)
unmergedPaths.add(tw.getPathString());
@@ -760,6 +837,7 @@ private void updateIndex(CanonicalTreeParser base,
MergeResult<RawText> result) throws FileNotFoundException,
IOException {
File mergedFile = !inCore ? writeMergedFile(result) : null;
+
if (result.containsConflicts()) {
// A conflict occurred, the file will contain conflict markers
// the index will be populated with the three stages and the
@@ -827,7 +905,7 @@ private File writeMergedFile(MergeResult<RawText> result)
private ObjectId insertMergeResult(MergeResult<RawText> result)
throws IOException {
TemporaryBuffer.LocalFile buf = new TemporaryBuffer.LocalFile(
- db != null ? nonNullRepo().getDirectory() : null, 10 << 20);
+ db != null ? nonNullRepo().getDirectory() : null, inCoreLimit);
try {
new MergeFormatter().formatMerge(buf, result,
Arrays.asList(commitNames), CHARACTER_ENCODING);
@@ -1091,6 +1169,8 @@ protected boolean mergeTrees(AbstractTreeIterator baseTree,
protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts)
throws IOException {
boolean hasWorkingTreeIterator = tw.getTreeCount() > T_FILE;
+ boolean hasAttributeNodeProvider = treeWalk
+ .getAttributesNodeProvider() != null;
while (treeWalk.next()) {
if (!processEntry(
treeWalk.getTree(T_BASE, CanonicalTreeParser.class),
@@ -1098,7 +1178,9 @@ protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts)
treeWalk.getTree(T_THEIRS, CanonicalTreeParser.class),
treeWalk.getTree(T_INDEX, DirCacheBuildIterator.class),
hasWorkingTreeIterator ? treeWalk.getTree(T_FILE,
- WorkingTreeIterator.class) : null, ignoreConflicts)) {
+ WorkingTreeIterator.class) : null,
+ ignoreConflicts, hasAttributeNodeProvider
+ ? treeWalk.getAttributes() : new Attributes())) {
cleanUp();
return false;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java b/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java
index c85c179..bde69c0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/nls/TranslationBundle.java
@@ -184,4 +184,4 @@ void load(Locale locale)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java
index e230c9b..51dd2ed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java
@@ -91,4 +91,4 @@ public boolean include(RevWalk walker, RevCommit cmit)
public RevFilter clone() {
return new SkipRevFilter(skip);
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
index c64aa2d..4f2374f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
@@ -260,6 +260,8 @@ public class PackConfig {
private boolean cutDeltaChains;
+ private boolean singlePack;
+
/** Create a default configuration. */
public PackConfig() {
// Fields are initialized to defaults.
@@ -320,6 +322,7 @@ public PackConfig(PackConfig cfg) {
this.bitmapExcessiveBranchCount = cfg.bitmapExcessiveBranchCount;
this.bitmapInactiveBranchAgeInDays = cfg.bitmapInactiveBranchAgeInDays;
this.cutDeltaChains = cfg.cutDeltaChains;
+ this.singlePack = cfg.singlePack;
}
/**
@@ -555,6 +558,30 @@ public void setCutDeltaChains(boolean cut) {
}
/**
+ * @return true if all of refs/* should be packed in a single pack. Default
+ * is false, packing a separate GC_REST pack for references outside
+ * of refs/heads/* and refs/tags/*.
+ * @since 4.9
+ */
+ public boolean getSinglePack() {
+ return singlePack;
+ }
+
+ /**
+ * If {@code true}, packs a single GC pack for all objects reachable from
+ * refs/*. Otherwise packs the GC pack with objects reachable from
+ * refs/heads/* and refs/tags/*, and a GC_REST pack with the remaining
+ * reachable objects. Disabled by default, packing GC and GC_REST.
+ *
+ * @param single
+ * true to pack a single GC pack rather than GC and GC_REST packs
+ * @since 4.9
+ */
+ public void setSinglePack(boolean single) {
+ singlePack = single;
+ }
+
+ /**
* Get the number of objects to try when looking for a delta base.
*
* This limit is per thread, if 4 threads are used the actual memory used
@@ -1026,6 +1053,8 @@ public void fromConfig(final Config rc) {
rc.getBoolean("pack", "deltacompression", isDeltaCompress())); //$NON-NLS-1$ //$NON-NLS-2$
setCutDeltaChains(
rc.getBoolean("pack", "cutdeltachains", getCutDeltaChains())); //$NON-NLS-1$ //$NON-NLS-2$
+ setSinglePack(
+ rc.getBoolean("pack", "singlepack", getSinglePack())); //$NON-NLS-1$ //$NON-NLS-2$
setBuildBitmaps(
rc.getBoolean("pack", "buildbitmaps", isBuildBitmaps())); //$NON-NLS-1$ //$NON-NLS-2$
setBitmapContiguousCommitCount(
@@ -1073,6 +1102,7 @@ public String toString() {
.append(getBitmapExcessiveBranchCount());
b.append(", bitmapInactiveBranchAge=") //$NON-NLS-1$
.append(getBitmapInactiveBranchAgeInDays());
+ b.append(", singlePack=").append(getSinglePack()); //$NON-NLS-1$
return b.toString();
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
index a10f3d7..56784f7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
@@ -45,7 +45,8 @@
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
-import java.util.Locale;
+import java.util.HashMap;
+import java.util.Map;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheIterator;
@@ -330,6 +331,8 @@ else if (submoduleUrl.startsWith("../")) { //$NON-NLS-1$
private String path;
+ private Map<String, String> pathToName;
+
/**
* Create submodule generator
*
@@ -355,6 +358,7 @@ public SubmoduleWalk(final Repository repository) throws IOException {
*/
public SubmoduleWalk setModulesConfig(final Config config) {
modulesConfig = config;
+ loadPathNames();
return this;
}
@@ -374,6 +378,7 @@ public SubmoduleWalk setModulesConfig(final Config config) {
public SubmoduleWalk setRootTree(final AbstractTreeIterator tree) {
rootTree = tree;
modulesConfig = null;
+ pathToName = null;
return this;
}
@@ -396,6 +401,7 @@ public SubmoduleWalk setRootTree(final AnyObjectId id) throws IOException {
p.reset(walk.getObjectReader(), id);
rootTree = p;
modulesConfig = null;
+ pathToName = null;
return this;
}
@@ -419,6 +425,7 @@ public SubmoduleWalk loadModulesConfig() throws IOException, ConfigInvalidExcept
repository.getFS());
config.load();
modulesConfig = config;
+ loadPathNames();
} else {
try (TreeWalk configWalk = new TreeWalk(repository)) {
configWalk.addTree(rootTree);
@@ -438,10 +445,12 @@ public SubmoduleWalk loadModulesConfig() throws IOException, ConfigInvalidExcept
if (filter.isDone(configWalk)) {
modulesConfig = new BlobBasedConfig(null, repository,
configWalk.getObjectId(0));
+ loadPathNames();
return this;
}
}
modulesConfig = new Config();
+ pathToName = null;
} finally {
if (idx > 0)
rootTree.next(idx);
@@ -451,6 +460,20 @@ public SubmoduleWalk loadModulesConfig() throws IOException, ConfigInvalidExcept
return this;
}
+ private void loadPathNames() {
+ pathToName = null;
+ if (modulesConfig != null) {
+ HashMap<String, String> pathNames = new HashMap<>();
+ for (String name : modulesConfig
+ .getSubsections(ConfigConstants.CONFIG_SUBMODULE_SECTION)) {
+ pathNames.put(modulesConfig.getString(
+ ConfigConstants.CONFIG_SUBMODULE_SECTION, name,
+ ConfigConstants.CONFIG_KEY_PATH), name);
+ }
+ pathToName = pathNames;
+ }
+ }
+
/**
* Checks whether the working tree contains a .gitmodules file. That's a
* hint that the repo contains submodules.
@@ -475,8 +498,14 @@ public static boolean containsGitModulesFile(Repository repository)
}
private void lazyLoadModulesConfig() throws IOException, ConfigInvalidException {
- if (modulesConfig == null)
+ if (modulesConfig == null) {
loadModulesConfig();
+ }
+ }
+
+ private String getModuleName(String modulePath) {
+ String name = pathToName != null ? pathToName.get(modulePath) : null;
+ return name != null ? name : modulePath;
}
/**
@@ -525,6 +554,7 @@ public SubmoduleWalk setTree(final AnyObjectId treeId) throws IOException {
public SubmoduleWalk reset() {
repoConfig = repository.getConfig();
modulesConfig = null;
+ pathToName = null;
walk.reset();
return this;
}
@@ -586,9 +616,8 @@ public ObjectId getObjectId() {
*/
public String getModulesPath() throws IOException, ConfigInvalidException {
lazyLoadModulesConfig();
- return modulesConfig.getString(
- ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
- ConfigConstants.CONFIG_KEY_PATH);
+ return modulesConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
+ getModuleName(path), ConfigConstants.CONFIG_KEY_PATH);
}
/**
@@ -600,6 +629,10 @@ public String getModulesPath() throws IOException, ConfigInvalidException {
* @throws IOException
*/
public String getConfigUrl() throws IOException, ConfigInvalidException {
+ // SubmoduleInitCommand copies the submodules.*.url and
+ // submodules.*.update values from .gitmodules to the config, and
+ // does so using the path defined in .gitmodules as the subsection
+ // name. So no path-to-name translation is necessary here.
return repoConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
path, ConfigConstants.CONFIG_KEY_URL);
}
@@ -614,9 +647,8 @@ public String getConfigUrl() throws IOException, ConfigInvalidException {
*/
public String getModulesUrl() throws IOException, ConfigInvalidException {
lazyLoadModulesConfig();
- return modulesConfig.getString(
- ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
- ConfigConstants.CONFIG_KEY_URL);
+ return modulesConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
+ getModuleName(path), ConfigConstants.CONFIG_KEY_URL);
}
/**
@@ -642,9 +674,8 @@ public String getConfigUpdate() throws IOException, ConfigInvalidException {
*/
public String getModulesUpdate() throws IOException, ConfigInvalidException {
lazyLoadModulesConfig();
- return modulesConfig.getString(
- ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
- ConfigConstants.CONFIG_KEY_UPDATE);
+ return modulesConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
+ getModuleName(path), ConfigConstants.CONFIG_KEY_UPDATE);
}
/**
@@ -659,13 +690,9 @@ public String getModulesUpdate() throws IOException, ConfigInvalidException {
public IgnoreSubmoduleMode getModulesIgnore() throws IOException,
ConfigInvalidException {
lazyLoadModulesConfig();
- String name = modulesConfig.getString(
- ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
- ConfigConstants.CONFIG_KEY_IGNORE);
- if (name == null)
- return null;
- return IgnoreSubmoduleMode
- .valueOf(name.trim().toUpperCase(Locale.ROOT));
+ return modulesConfig.getEnum(IgnoreSubmoduleMode.values(),
+ ConfigConstants.CONFIG_SUBMODULE_SECTION, getModuleName(path),
+ ConfigConstants.CONFIG_KEY_IGNORE, IgnoreSubmoduleMode.NONE);
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
index e8d1881..61c4c4b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
@@ -63,7 +63,6 @@
import org.eclipse.jgit.internal.storage.file.PackLock;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -250,7 +249,7 @@ public BasePackFetchConnection(final PackTransport packTransport) {
super(packTransport);
if (local != null) {
- final FetchConfig cfg = local.getConfig().get(FetchConfig.KEY);
+ final FetchConfig cfg = local.getConfig().get(FetchConfig::new);
allowOfsDelta = cfg.allowOfsDelta;
} else {
allowOfsDelta = true;
@@ -279,13 +278,6 @@ public BasePackFetchConnection(final PackTransport packTransport) {
}
private static class FetchConfig {
- static final SectionParser<FetchConfig> KEY = new SectionParser<FetchConfig>() {
- @Override
- public FetchConfig parse(final Config cfg) {
- return new FetchConfig(cfg);
- }
- };
-
final boolean allowOfsDelta;
FetchConfig(final Config c) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
index 14b683f..72c8664 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
@@ -81,7 +81,6 @@
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.GitmoduleEntry;
import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -320,7 +319,7 @@ protected BaseReceivePack(final Repository into) {
TransferConfig tc = db.getConfig().get(TransferConfig.KEY);
objectChecker = tc.newReceiveObjectChecker();
- ReceiveConfig rc = db.getConfig().get(ReceiveConfig.KEY);
+ ReceiveConfig rc = db.getConfig().get(ReceiveConfig::new);
allowCreates = rc.allowCreates;
allowAnyDeletes = true;
allowBranchDeletes = rc.allowDeletes;
@@ -338,13 +337,6 @@ protected BaseReceivePack(final Repository into) {
/** Configuration for receive operations. */
protected static class ReceiveConfig {
- static final SectionParser<ReceiveConfig> KEY = new SectionParser<ReceiveConfig>() {
- @Override
- public ReceiveConfig parse(final Config cfg) {
- return new ReceiveConfig(cfg);
- }
- };
-
final boolean allowCreates;
final boolean allowDeletes;
final boolean allowNonFastForwards;
@@ -461,6 +453,7 @@ public final Map<String, Ref> getAdvertisedRefs() {
public void setAdvertisedRefs(Map<String, Ref> allRefs, Set<ObjectId> additionalHaves) {
refs = allRefs != null ? allRefs : db.getAllRefs();
refs = refFilter.filter(refs);
+ advertisedHaves.clear();
Ref head = refs.get(Constants.HEAD);
if (head != null && head.isSymbolic())
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
index 40b2c47..896b10a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java
@@ -45,13 +45,14 @@
import java.io.IOException;
import java.io.InputStream;
-import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
+import java.net.SocketException;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.internal.JGitText;
@@ -77,9 +78,7 @@ public class Daemon {
private final ThreadGroup processors;
- private boolean run;
-
- Thread acceptThread;
+ private Acceptor acceptThread;
private int timeout;
@@ -281,6 +280,56 @@ public void setReceivePackFactory(ReceivePackFactory<DaemonClient> factory) {
receivePackFactory = (ReceivePackFactory<DaemonClient>) ReceivePackFactory.DISABLED;
}
+ private class Acceptor extends Thread {
+
+ private final ServerSocket listenSocket;
+
+ private final AtomicBoolean running = new AtomicBoolean(true);
+
+ public Acceptor(ThreadGroup group, String name, ServerSocket socket) {
+ super(group, name);
+ this.listenSocket = socket;
+ }
+
+ @Override
+ public void run() {
+ setUncaughtExceptionHandler((thread, throwable) -> terminate());
+ while (isRunning()) {
+ try {
+ startClient(listenSocket.accept());
+ } catch (SocketException e) {
+ // Test again to see if we should keep accepting.
+ } catch (IOException e) {
+ break;
+ }
+ }
+
+ terminate();
+ }
+
+ private void terminate() {
+ try {
+ shutDown();
+ } finally {
+ clearThread();
+ }
+ }
+
+ public boolean isRunning() {
+ return running.get();
+ }
+
+ public void shutDown() {
+ running.set(false);
+ try {
+ listenSocket.close();
+ } catch (IOException err) {
+ //
+ }
+ }
+
+ }
+
/**
* Start this daemon on a background thread.
*
@@ -290,52 +339,56 @@ public void setReceivePackFactory(ReceivePackFactory<DaemonClient> factory) {
* the daemon is already running.
*/
public synchronized void start() throws IOException {
- if (acceptThread != null)
+ if (acceptThread != null) {
throw new IllegalStateException(JGitText.get().daemonAlreadyRunning);
+ }
+ ServerSocket socket = new ServerSocket();
+ socket.setReuseAddress(true);
+ if (myAddress != null) {
+ socket.bind(myAddress, BACKLOG);
+ } else {
+ socket.bind(new InetSocketAddress((InetAddress) null, 0), BACKLOG);
+ }
+ myAddress = (InetSocketAddress) socket.getLocalSocketAddress();
- final ServerSocket listenSock = new ServerSocket(
- myAddress != null ? myAddress.getPort() : 0, BACKLOG,
- myAddress != null ? myAddress.getAddress() : null);
- myAddress = (InetSocketAddress) listenSock.getLocalSocketAddress();
-
- run = true;
- acceptThread = new Thread(processors, "Git-Daemon-Accept") { //$NON-NLS-1$
- @Override
- public void run() {
- while (isRunning()) {
- try {
- startClient(listenSock.accept());
- } catch (InterruptedIOException e) {
- // Test again to see if we should keep accepting.
- } catch (IOException e) {
- break;
- }
- }
-
- try {
- listenSock.close();
- } catch (IOException err) {
- //
- } finally {
- synchronized (Daemon.this) {
- acceptThread = null;
- }
- }
- }
- };
+ acceptThread = new Acceptor(processors, "Git-Daemon-Accept", socket); //$NON-NLS-1$
acceptThread.start();
}
+ private synchronized void clearThread() {
+ acceptThread = null;
+ }
+
/** @return true if this daemon is receiving connections. */
public synchronized boolean isRunning() {
- return run;
+ return acceptThread != null && acceptThread.isRunning();
}
- /** Stop this daemon. */
+ /**
+ * Stop this daemon.
+ */
public synchronized void stop() {
if (acceptThread != null) {
- run = false;
- acceptThread.interrupt();
+ acceptThread.shutDown();
+ }
+ }
+
+ /**
+ * Stops this daemon and waits until it's acceptor thread has finished.
+ *
+ * @throws InterruptedException
+ * if waiting for the acceptor thread is interrupted
+ *
+ * @since 4.9
+ */
+ public void stopAndWait() throws InterruptedException {
+ Thread acceptor = null;
+ synchronized (this) {
+ acceptor = acceptThread;
+ stop();
+ }
+ if (acceptor != null) {
+ acceptor.join();
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java
index 80b2cae..566153a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java
@@ -64,12 +64,7 @@ public abstract class DaemonService {
DaemonService(final String cmdName, final String cfgName) {
command = cmdName.startsWith("git-") ? cmdName : "git-" + cmdName; //$NON-NLS-1$ //$NON-NLS-2$
- configKey = new SectionParser<ServiceConfig>() {
- @Override
- public ServiceConfig parse(final Config cfg) {
- return new ServiceConfig(DaemonService.this, cfg, cfgName);
- }
- };
+ configKey = cfg -> new ServiceConfig(DaemonService.this, cfg, cfgName);
overridable = true;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
index 280e6d4..ed10f44 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
@@ -74,6 +74,7 @@
import org.eclipse.jgit.lib.BatchingProgressMonitor;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefDatabase;
@@ -202,12 +203,10 @@ else if (tagopt == TagOpt.FETCH_TAGS)
((BatchingProgressMonitor) monitor).setDelayStart(
250, TimeUnit.MILLISECONDS);
}
- if (transport.isRemoveDeletedRefs())
+ if (transport.isRemoveDeletedRefs()) {
deleteStaleTrackingRefs(result, batch);
- for (TrackingRefUpdate u : localUpdates) {
- result.add(u);
- batch.addCommand(u.asReceiveCommand());
}
+ addUpdateBatchCommands(result, batch);
for (ReceiveCommand cmd : batch.getCommands()) {
cmd.updateType(walk);
if (cmd.getType() == UPDATE_NONFASTFORWARD
@@ -220,8 +219,11 @@ else if (tagopt == TagOpt.FETCH_TAGS)
if (cmd.getResult() == NOT_ATTEMPTED)
cmd.setResult(OK);
}
- } else
+ } else {
batch.execute(walk, monitor);
+ }
+ } catch (TransportException e) {
+ throw e;
} catch (IOException err) {
throw new TransportException(MessageFormat.format(
JGitText.get().failureUpdatingTrackingRef,
@@ -238,6 +240,23 @@ else if (tagopt == TagOpt.FETCH_TAGS)
}
}
+ private void addUpdateBatchCommands(FetchResult result,
+ BatchRefUpdate batch) throws TransportException {
+ Map<String, ObjectId> refs = new HashMap<>();
+ for (TrackingRefUpdate u : localUpdates) {
+ // Try to skip duplicates if they'd update to the same object ID
+ ObjectId existing = refs.get(u.getLocalName());
+ if (existing == null) {
+ refs.put(u.getLocalName(), u.getNewObjectId());
+ result.add(u);
+ batch.addCommand(u.asReceiveCommand());
+ } else if (!existing.equals(u.getNewObjectId())) {
+ throw new TransportException(MessageFormat
+ .format(JGitText.get().duplicateRef, u.getLocalName()));
+ }
+ }
+ }
+
private void fetchObjects(final ProgressMonitor monitor)
throws TransportException {
try {
@@ -360,12 +379,19 @@ private void expandWildcard(final RefSpec spec, final Set<Ref> matched)
private void expandSingle(final RefSpec spec, final Set<Ref> matched)
throws TransportException {
- final Ref src = conn.getRef(spec.getSource());
- if (src == null) {
- throw new TransportException(MessageFormat.format(JGitText.get().remoteDoesNotHaveSpec, spec.getSource()));
+ String want = spec.getSource();
+ if (ObjectId.isId(want)) {
+ want(ObjectId.fromString(want));
+ return;
}
- if (matched.add(src))
+
+ Ref src = conn.getRef(want);
+ if (src == null) {
+ throw new TransportException(MessageFormat.format(JGitText.get().remoteDoesNotHaveSpec, want));
+ }
+ if (matched.add(src)) {
want(src, spec);
+ }
}
private Collection<Ref> expandAutoFollowTags() throws TransportException {
@@ -440,6 +466,11 @@ private void want(final Ref src, final RefSpec spec)
fetchHeadUpdates.add(fhr);
}
+ private void want(ObjectId id) {
+ askFor.put(id,
+ new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, id.name(), id));
+ }
+
private TrackingRefUpdate createUpdate(RefSpec spec, ObjectId newId)
throws TransportException {
Ref ref = localRefs().get(spec.getDestination());
@@ -468,12 +499,14 @@ private Map<String, Ref> localRefs() throws TransportException {
private void deleteStaleTrackingRefs(FetchResult result,
BatchRefUpdate batch) throws IOException {
+ final Set<Ref> processed = new HashSet<>();
for (final Ref ref : localRefs().values()) {
final String refname = ref.getName();
for (final RefSpec spec : toFetch) {
if (spec.matchDestination(refname)) {
final RefSpec s = spec.expandFromDestination(refname);
- if (result.getAdvertisedRef(s.getSource()) == null) {
+ if (result.getAdvertisedRef(s.getSource()) == null
+ && processed.add(ref)) {
deleteTrackingRef(result, batch, s, ref);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
new file mode 100644
index 0000000..db59a54
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2008, 2010, Google Inc.
+ * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.text.MessageFormat;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.StringUtils;
+import org.eclipse.jgit.util.SystemReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A representation of the "http.*" config values in a git {@link Config}. git
+ * provides for setting values for specific URLs through "http.<url>.*
+ * subsections. git always considers only the initial original URL for such
+ * settings, not any redirected URL.
+ *
+ * @since 4.9
+ */
+public class HttpConfig {
+
+ private static final Logger LOG = LoggerFactory.getLogger(HttpConfig.class);
+
+ private static final String FTP = "ftp"; //$NON-NLS-1$
+
+ /** git config section key for http settings. */
+ public static final String HTTP = "http"; //$NON-NLS-1$
+
+ /** git config key for the "followRedirects" setting. */
+ public static final String FOLLOW_REDIRECTS_KEY = "followRedirects"; //$NON-NLS-1$
+
+ /** git config key for the "maxRedirects" setting. */
+ public static final String MAX_REDIRECTS_KEY = "maxRedirects"; //$NON-NLS-1$
+
+ /** git config key for the "postBuffer" setting. */
+ public static final String POST_BUFFER_KEY = "postBuffer"; //$NON-NLS-1$
+
+ /** git config key for the "sslVerify" setting. */
+ public static final String SSL_VERIFY_KEY = "sslVerify"; //$NON-NLS-1$
+
+ private static final String MAX_REDIRECT_SYSTEM_PROPERTY = "http.maxRedirects"; //$NON-NLS-1$
+
+ private static final int DEFAULT_MAX_REDIRECTS = 5;
+
+ private static final int MAX_REDIRECTS = (new Supplier<Integer>() {
+
+ @Override
+ public Integer get() {
+ String rawValue = SystemReader.getInstance()
+ .getProperty(MAX_REDIRECT_SYSTEM_PROPERTY);
+ Integer value = Integer.valueOf(DEFAULT_MAX_REDIRECTS);
+ if (rawValue != null) {
+ try {
+ value = Integer.valueOf(Integer.parseUnsignedInt(rawValue));
+ } catch (NumberFormatException e) {
+ LOG.warn(MessageFormat.format(
+ JGitText.get().invalidSystemProperty,
+ MAX_REDIRECT_SYSTEM_PROPERTY, rawValue, value));
+ }
+ }
+ return value;
+ }
+ }).get().intValue();
+
+ /**
+ * Config values for http.followRedirect.
+ */
+ public enum HttpRedirectMode implements Config.ConfigEnum {
+
+ /** Always follow redirects (up to the http.maxRedirects limit). */
+ TRUE("true"), //$NON-NLS-1$
+ /**
+ * Only follow redirects on the initial GET request. This is the
+ * default.
+ */
+ INITIAL("initial"), //$NON-NLS-1$
+ /** Never follow redirects. */
+ FALSE("false"); //$NON-NLS-1$
+
+ private final String configValue;
+
+ private HttpRedirectMode(String configValue) {
+ this.configValue = configValue;
+ }
+
+ @Override
+ public String toConfigValue() {
+ return configValue;
+ }
+
+ @Override
+ public boolean matchConfigValue(String s) {
+ return configValue.equals(s);
+ }
+ }
+
+ private int postBuffer;
+
+ private boolean sslVerify;
+
+ private HttpRedirectMode followRedirects;
+
+ private int maxRedirects;
+
+ /**
+ * @return the value of the "http.postBuffer" setting
+ */
+ public int getPostBuffer() {
+ return postBuffer;
+ }
+
+ /**
+ * @return the value of the "http.sslVerify" setting
+ */
+ public boolean isSslVerify() {
+ return sslVerify;
+ }
+
+ /**
+ * @return the value of the "http.followRedirects" setting
+ */
+ public HttpRedirectMode getFollowRedirects() {
+ return followRedirects;
+ }
+
+ /**
+ * @return the value of the "http.maxRedirects" setting
+ */
+ public int getMaxRedirects() {
+ return maxRedirects;
+ }
+
+ /**
+ * Creates a new {@link HttpConfig} tailored to the given {@link URIish}.
+ *
+ * @param config
+ * to read the {@link HttpConfig} from
+ * @param uri
+ * to get the configuration values for
+ */
+ public HttpConfig(Config config, URIish uri) {
+ init(config, uri);
+ }
+
+ /**
+ * Creates a {@link HttpConfig} that reads values solely from the user
+ * config.
+ *
+ * @param uri
+ * to get the configuration values for
+ */
+ public HttpConfig(URIish uri) {
+ FileBasedConfig userConfig = SystemReader.getInstance()
+ .openUserConfig(null, FS.DETECTED);
+ try {
+ userConfig.load();
+ } catch (IOException | ConfigInvalidException e) {
+ // Log it and then work with default values.
+ LOG.error(MessageFormat.format(JGitText.get().userConfigFileInvalid,
+ userConfig.getFile().getAbsolutePath(), e));
+ init(new Config(), uri);
+ return;
+ }
+ init(userConfig, uri);
+ }
+
+ private void init(Config config, URIish uri) {
+ // Set defaults from the section first
+ int postBufferSize = config.getInt(HTTP, POST_BUFFER_KEY,
+ 1 * 1024 * 1024);
+ boolean sslVerifyFlag = config.getBoolean(HTTP, SSL_VERIFY_KEY, true);
+ HttpRedirectMode followRedirectsMode = config.getEnum(
+ HttpRedirectMode.values(), HTTP, null,
+ FOLLOW_REDIRECTS_KEY, HttpRedirectMode.INITIAL);
+ int redirectLimit = config.getInt(HTTP, MAX_REDIRECTS_KEY,
+ MAX_REDIRECTS);
+ if (redirectLimit < 0) {
+ redirectLimit = MAX_REDIRECTS;
+ }
+ String match = findMatch(config.getSubsections(HTTP), uri);
+ if (match != null) {
+ // Override with more specific items
+ postBufferSize = config.getInt(HTTP, match, POST_BUFFER_KEY,
+ postBufferSize);
+ sslVerifyFlag = config.getBoolean(HTTP, match, SSL_VERIFY_KEY,
+ sslVerifyFlag);
+ followRedirectsMode = config.getEnum(HttpRedirectMode.values(),
+ HTTP, match, FOLLOW_REDIRECTS_KEY, followRedirectsMode);
+ int newMaxRedirects = config.getInt(HTTP, match, MAX_REDIRECTS_KEY,
+ redirectLimit);
+ if (newMaxRedirects >= 0) {
+ redirectLimit = newMaxRedirects;
+ }
+ }
+ postBuffer = postBufferSize;
+ sslVerify = sslVerifyFlag;
+ followRedirects = followRedirectsMode;
+ maxRedirects = redirectLimit;
+ }
+
+ /**
+ * Determines the best match from a set of subsection names (representing
+ * prefix URLs) for the given {@link URIish}.
+ *
+ * @param names
+ * to match against the {@code uri}
+ * @param uri
+ * to find a match for
+ * @return the best matching subsection name, or {@code null} if no
+ * subsection matches
+ */
+ private String findMatch(Set<String> names, URIish uri) {
+ String bestMatch = null;
+ int bestMatchLength = -1;
+ boolean withUser = false;
+ String uPath = uri.getPath();
+ boolean hasPath = !StringUtils.isEmptyOrNull(uPath);
+ if (hasPath) {
+ uPath = normalize(uPath);
+ if (uPath == null) {
+ // Normalization failed; warning was logged.
+ return null;
+ }
+ }
+ for (String s : names) {
+ try {
+ URIish candidate = new URIish(s);
+ // Scheme and host must match case-insensitively
+ if (!compare(uri.getScheme(), candidate.getScheme())
+ || !compare(uri.getHost(), candidate.getHost())) {
+ continue;
+ }
+ // Ports must match after default ports have been substituted
+ if (defaultedPort(uri.getPort(),
+ uri.getScheme()) != defaultedPort(candidate.getPort(),
+ candidate.getScheme())) {
+ continue;
+ }
+ // User: if present in candidate, must match
+ boolean hasUser = false;
+ if (candidate.getUser() != null) {
+ if (!candidate.getUser().equals(uri.getUser())) {
+ continue;
+ }
+ hasUser = true;
+ }
+ // Path: prefix match, longer is better
+ String cPath = candidate.getPath();
+ int matchLength = -1;
+ if (StringUtils.isEmptyOrNull(cPath)) {
+ matchLength = 0;
+ } else {
+ if (!hasPath) {
+ continue;
+ }
+ // Paths can match only on segments
+ matchLength = segmentCompare(uPath, cPath);
+ if (matchLength < 0) {
+ continue;
+ }
+ }
+ // A longer path match is always preferred even over a user
+ // match. If the path matches are equal, a match with user wins
+ // over a match without user.
+ if (matchLength > bestMatchLength || !withUser && hasUser
+ && matchLength >= 0 && matchLength == bestMatchLength) {
+ bestMatch = s;
+ bestMatchLength = matchLength;
+ withUser = hasUser;
+ }
+ } catch (URISyntaxException e) {
+ LOG.warn(MessageFormat
+ .format(JGitText.get().httpConfigInvalidURL, s));
+ }
+ }
+ return bestMatch;
+ }
+
+ private boolean compare(String a, String b) {
+ if (a == null) {
+ return b == null;
+ }
+ return a.equalsIgnoreCase(b);
+ }
+
+ private int defaultedPort(int port, String scheme) {
+ if (port >= 0) {
+ return port;
+ }
+ if (FTP.equalsIgnoreCase(scheme)) {
+ return 21;
+ } else if (HTTP.equalsIgnoreCase(scheme)) {
+ return 80;
+ } else {
+ return 443; // https
+ }
+ }
+
+ static int segmentCompare(String uriPath, String m) {
+ // Precondition: !uriPath.isEmpty() && !m.isEmpty(),and u must already
+ // be normalized
+ String matchPath = normalize(m);
+ if (matchPath == null || !uriPath.startsWith(matchPath)) {
+ return -1;
+ }
+ // We can match only on a segment boundary: either both paths are equal,
+ // or if matchPath does not end in '/', there is a '/' in uriPath right
+ // after the match.
+ int uLength = uriPath.length();
+ int mLength = matchPath.length();
+ if (mLength == uLength || matchPath.charAt(mLength - 1) == '/'
+ || mLength < uLength && uriPath.charAt(mLength) == '/') {
+ return mLength;
+ }
+ return -1;
+ }
+
+ static String normalize(String path) {
+ // C-git resolves . and .. segments
+ int i = 0;
+ int length = path.length();
+ StringBuilder builder = new StringBuilder(length);
+ builder.append('/');
+ if (length > 0 && path.charAt(0) == '/') {
+ i = 1;
+ }
+ while (i < length) {
+ int slash = path.indexOf('/', i);
+ if (slash < 0) {
+ slash = length;
+ }
+ if (slash == i || slash == i + 1 && path.charAt(i) == '.') {
+ // Skip /. or also double slashes
+ } else if (slash == i + 2 && path.charAt(i) == '.'
+ && path.charAt(i + 1) == '.') {
+ // Remove previous segment if we have "/.."
+ int l = builder.length() - 2; // Skip terminating slash.
+ while (l >= 0 && builder.charAt(l) != '/') {
+ l--;
+ }
+ if (l < 0) {
+ LOG.warn(MessageFormat.format(
+ JGitText.get().httpConfigCannotNormalizeURL, path));
+ return null;
+ }
+ builder.setLength(l + 1);
+ } else {
+ // Include the slash, if any
+ builder.append(path, i, Math.min(length, slash + 1));
+ }
+ i = slash + 1;
+ }
+ if (builder.length() > 1 && builder.charAt(builder.length() - 1) == '/'
+ && length > 0 && path.charAt(length - 1) != '/') {
+ // . or .. normalization left a trailing slash when the original
+ // path had none at the end
+ builder.setLength(builder.length() - 1);
+ }
+ return builder.toString();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
index ce14183..0cc40f3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java
@@ -53,15 +53,23 @@
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.net.ConnectException;
import java.net.UnknownHostException;
+import java.text.MessageFormat;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.util.FS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.jcraft.jsch.ConfigRepository;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
@@ -80,6 +88,18 @@
* to supply appropriate {@link UserInfo} to the session.
*/
public abstract class JschConfigSessionFactory extends SshSessionFactory {
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(JschConfigSessionFactory.class);
+
+ /**
+ * We use different Jsch instances for hosts that have an IdentityFile
+ * configured in ~/.ssh/config. Jsch by default would cache decrypted keys
+ * only per session, which results in repeated password prompts. Using
+ * different Jsch instances, we can cache the keys on these instances so
+ * that they will be re-used for successive sessions, and thus the user is
+ * prompted for a key password only once while Eclipse runs.
+ */
private final Map<String, JSch> byIdentityFile = new HashMap<>();
private JSch defaultJSch;
@@ -101,7 +121,6 @@ public synchronized RemoteSession getSession(URIish uri,
config = OpenSshConfig.get(fs);
final OpenSshConfig.Host hc = config.lookup(host);
- host = hc.getHostName();
if (port <= 0)
port = hc.getPort();
if (user == null)
@@ -153,10 +172,13 @@ public synchronized RemoteSession getSession(URIish uri,
} catch (JSchException je) {
final Throwable c = je.getCause();
- if (c instanceof UnknownHostException)
- throw new TransportException(uri, JGitText.get().unknownHost);
- if (c instanceof ConnectException)
- throw new TransportException(uri, c.getMessage());
+ if (c instanceof UnknownHostException) {
+ throw new TransportException(uri, JGitText.get().unknownHost,
+ je);
+ }
+ if (c instanceof ConnectException) {
+ throw new TransportException(uri, c.getMessage(), je);
+ }
throw new TransportException(uri, je.getMessage(), je);
}
@@ -170,10 +192,18 @@ private static boolean isAuthenticationCanceled(JSchException e) {
return e.getCause() == null && e.getMessage().equals("Auth cancel"); //$NON-NLS-1$
}
- private Session createSession(CredentialsProvider credentialsProvider,
+ // Package visibility for tests
+ Session createSession(CredentialsProvider credentialsProvider,
FS fs, String user, final String pass, String host, int port,
final OpenSshConfig.Host hc) throws JSchException {
final Session session = createSession(hc, user, host, port, fs);
+ // Jsch will have overridden the explicit user by the one from the SSH
+ // config file...
+ setUserName(session, user);
+ // Jsch will also have overridden the port.
+ if (port > 0 && port != session.getPort()) {
+ session.setPort(port);
+ }
// We retry already in getSession() method. JSch must not retry
// on its own.
session.setConfig("MaxAuthTries", "1"); //$NON-NLS-1$ //$NON-NLS-2$
@@ -196,6 +226,28 @@ private Session createSession(CredentialsProvider credentialsProvider,
return session;
}
+ private void setUserName(Session session, String userName) {
+ // Jsch 0.1.54 picks up the user name from the ssh config, even if an
+ // explicit user name was given! We must correct that if ~/.ssh/config
+ // has a different user name.
+ if (userName == null || userName.isEmpty()
+ || userName.equals(session.getUserName())) {
+ return;
+ }
+ try {
+ Class<?>[] parameterTypes = { String.class };
+ Method method = Session.class.getDeclaredMethod("setUserName", //$NON-NLS-1$
+ parameterTypes);
+ method.setAccessible(true);
+ method.invoke(session, userName);
+ } catch (NullPointerException | IllegalAccessException
+ | IllegalArgumentException | InvocationTargetException
+ | NoSuchMethodException | SecurityException e) {
+ LOG.error(MessageFormat.format(JGitText.get().sshUserNameError,
+ userName, session.getUserName()), e);
+ }
+ }
+
/**
* Create a new remote session for the requested address.
*
@@ -259,6 +311,10 @@ protected void configureJSch(JSch jsch) {
protected JSch getJSch(final OpenSshConfig.Host hc, FS fs) throws JSchException {
if (defaultJSch == null) {
defaultJSch = createDefaultJSch(fs);
+ if (defaultJSch.getConfigRepository() == null) {
+ defaultJSch.setConfigRepository(
+ new JschBugFixingConfigRepository(config));
+ }
for (Object name : defaultJSch.getIdentityNames())
byIdentityFile.put((String) name, defaultJSch);
}
@@ -272,6 +328,9 @@ protected JSch getJSch(final OpenSshConfig.Host hc, FS fs) throws JSchException
if (jsch == null) {
jsch = new JSch();
configureJSch(jsch);
+ if (jsch.getConfigRepository() == null) {
+ jsch.setConfigRepository(defaultJSch.getConfigRepository());
+ }
jsch.setHostKeyRepository(defaultJSch.getHostKeyRepository());
jsch.addIdentity(identityKey);
byIdentityFile.put(identityKey, jsch);
@@ -335,4 +394,101 @@ private static void loadIdentity(final JSch sch, final File priv) {
}
}
}
+
+ private static class JschBugFixingConfigRepository
+ implements ConfigRepository {
+
+ private final ConfigRepository base;
+
+ public JschBugFixingConfigRepository(ConfigRepository base) {
+ this.base = base;
+ }
+
+ @Override
+ public Config getConfig(String host) {
+ return new JschBugFixingConfig(base.getConfig(host));
+ }
+
+ /**
+ * A {@link com.jcraft.jsch.ConfigRepository.Config} that transforms
+ * some values from the config file into the format Jsch 0.1.54 expects.
+ * This is a work-around for bugs in Jsch.
+ * <p>
+ * Additionally, this config hides the IdentityFile config entries from
+ * Jsch; we manage those ourselves. Otherwise Jsch would cache passwords
+ * (or rather, decrypted keys) only for a single session, resulting in
+ * multiple password prompts for user operations that use several Jsch
+ * sessions.
+ */
+ private static class JschBugFixingConfig implements Config {
+
+ private static final String[] NO_IDENTITIES = {};
+
+ private final Config real;
+
+ public JschBugFixingConfig(Config delegate) {
+ real = delegate;
+ }
+
+ @Override
+ public String getHostname() {
+ return real.getHostname();
+ }
+
+ @Override
+ public String getUser() {
+ return real.getUser();
+ }
+
+ @Override
+ public int getPort() {
+ return real.getPort();
+ }
+
+ @Override
+ public String getValue(String key) {
+ String k = key.toUpperCase(Locale.ROOT);
+ if ("IDENTITYFILE".equals(k)) { //$NON-NLS-1$
+ return null;
+ }
+ String result = real.getValue(key);
+ if (result != null) {
+ if ("SERVERALIVEINTERVAL".equals(k) //$NON-NLS-1$
+ || "CONNECTTIMEOUT".equals(k)) { //$NON-NLS-1$
+ // These values are in seconds. Jsch 0.1.54 passes them
+ // on as is to java.net.Socket.setSoTimeout(), which
+ // expects milliseconds. So convert here to
+ // milliseconds.
+ try {
+ int timeout = Integer.parseInt(result);
+ result = Long.toString(
+ TimeUnit.SECONDS.toMillis(timeout));
+ } catch (NumberFormatException e) {
+ // Ignore
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public String[] getValues(String key) {
+ String k = key.toUpperCase(Locale.ROOT);
+ if ("IDENTITYFILE".equals(k)) { //$NON-NLS-1$
+ return NO_IDENTITIES;
+ }
+ return real.getValues(key);
+ }
+ }
+ }
+
+ /**
+ * Set the {@link OpenSshConfig} to use. Intended for use in tests.
+ *
+ * @param config
+ * to use
+ */
+ void setConfig(OpenSshConfig config) {
+ this.config = config;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
index f445bcb..a8cc032 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
@@ -220,6 +220,7 @@ private boolean isRunning() {
public void destroy() {
if (channel.isConnected())
channel.disconnect();
+ closeOutputStream();
}
@Override
@@ -229,4 +230,4 @@ public int waitFor() throws InterruptedException {
return exitValue();
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
index bab5bf0..5727b03 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
@@ -317,4 +317,4 @@ private void parse() {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
index 8b7b60d..b5d5099 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008, 2014, Google Inc.
+ * Copyright (C) 2008, 2017, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -46,32 +46,90 @@
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
-import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import org.eclipse.jgit.errors.InvalidPatternException;
import org.eclipse.jgit.fnmatch.FileNameMatcher;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.StringUtils;
+import org.eclipse.jgit.util.SystemReader;
+
+import com.jcraft.jsch.ConfigRepository;
/**
- * Simple configuration parser for the OpenSSH ~/.ssh/config file.
+ * Fairly complete configuration parser for the OpenSSH ~/.ssh/config file.
* <p>
- * Since JSch does not (currently) have the ability to parse an OpenSSH
- * configuration file this is a simple parser to read that file and make the
- * critical options available to {@link SshSessionFactory}.
+ * JSch does have its own config file parser
+ * {@link com.jcraft.jsch.OpenSSHConfig} since version 0.1.50, but it has a
+ * number of problems:
+ * <ul>
+ * <li>it splits lines of the format "keyword = value" wrongly: you'd end up
+ * with the value "= value".
+ * <li>its "Host" keyword is not case insensitive.
+ * <li>it doesn't handle quoted values.
+ * <li>JSch's OpenSSHConfig doesn't monitor for config file changes.
+ * </ul>
+ * <p>
+ * Therefore implement our own parser to read an OpenSSH configuration file. It
+ * makes the critical options available to {@link SshSessionFactory} via
+ * {@link Host} objects returned by {@link #lookup(String)}, and implements a
+ * fully conforming {@link ConfigRepository} providing
+ * {@link com.jcraft.jsch.ConfigRepository.Config}s via
+ * {@link #getConfig(String)}.
+ * </p>
+ * <p>
+ * Limitations compared to the full OpenSSH 7.5 parser:
+ * </p>
+ * <ul>
+ * <li>This parser does not handle Match or Include keywords.
+ * <li>This parser does not do host name canonicalization (Jsch ignores it
+ * anyway).
+ * </ul>
+ * <p>
+ * Note that OpenSSH's readconf.c is a validating parser; Jsch's
+ * ConfigRepository OTOH treats all option values as plain strings, so any
+ * validation must happen in Jsch outside of the parser. Thus this parser does
+ * not validate option values, except for a few options when constructing a
+ * {@link Host} object.
+ * </p>
+ * <p>
+ * This config does %-substitutions for the following tokens:
+ * </p>
+ * <ul>
+ * <li>%% - single %
+ * <li>%C - short-hand for %l%h%p%r. See %p and %r below; the replacement may be
+ * done partially only and may leave %p or %r or both unreplaced.
+ * <li>%d - home directory path
+ * <li>%h - remote host name
+ * <li>%L - local host name without domain
+ * <li>%l - FQDN of the local host
+ * <li>%n - host name as specified in {@link #lookup(String)}
+ * <li>%p - port number; replaced only if set in the config
+ * <li>%r - remote user name; replaced only if set in the config
+ * <li>%u - local user name
+ * </ul>
+ * <p>
+ * If the config doesn't set the port or the remote user name, %p and %r remain
+ * un-substituted. It's the caller's responsibility to replace them with values
+ * obtained from the connection URI. %i is not handled; Java has no concept of a
+ * "user ID".
+ * </p>
*/
-public class OpenSshConfig {
+public class OpenSshConfig implements ConfigRepository {
+
/** IANA assigned port number for SSH. */
static final int SSH_PORT = 22;
@@ -105,16 +163,31 @@ public static OpenSshConfig get(FS fs) {
/** The .ssh/config file we read and monitor for updates. */
private final File configFile;
- /** Modification time of {@link #configFile} when {@link #hosts} loaded. */
+ /** Modification time of {@link #configFile} when it was last loaded. */
private long lastModified;
- /** Cached entries read out of the configuration file. */
- private Map<String, Host> hosts;
+ /**
+ * Encapsulates entries read out of the configuration file, and
+ * {@link Host}s created from that.
+ */
+ private static class State {
+ Map<String, HostEntry> entries = new LinkedHashMap<>();
+ Map<String, Host> hosts = new HashMap<>();
+
+ @Override
+ @SuppressWarnings("nls")
+ public String toString() {
+ return "State [entries=" + entries + ", hosts=" + hosts + "]";
+ }
+ }
+
+ /** State read from the config file, plus {@link Host}s created from it. */
+ private State state;
OpenSshConfig(final File h, final File cfg) {
home = h;
configFile = cfg;
- hosts = Collections.emptyMap();
+ state = new State();
}
/**
@@ -127,75 +200,81 @@ public static OpenSshConfig get(FS fs) {
* @return r configuration for the requested name. Never null.
*/
public Host lookup(final String hostName) {
- final Map<String, Host> cache = refresh();
- Host h = cache.get(hostName);
- if (h == null)
- h = new Host();
- if (h.patternsApplied)
+ final State cache = refresh();
+ Host h = cache.hosts.get(hostName);
+ if (h != null) {
return h;
-
- for (final Map.Entry<String, Host> e : cache.entrySet()) {
- if (!isHostPattern(e.getKey()))
- continue;
- if (!isHostMatch(e.getKey(), hostName))
- continue;
- h.copyFrom(e.getValue());
}
-
- if (h.hostName == null)
- h.hostName = hostName;
- if (h.user == null)
- h.user = OpenSshConfig.userName();
- if (h.port == 0)
- h.port = OpenSshConfig.SSH_PORT;
- if (h.connectionAttempts == 0)
- h.connectionAttempts = 1;
- h.patternsApplied = true;
+ HostEntry fullConfig = new HostEntry();
+ // Initialize with default entries at the top of the file, before the
+ // first Host block.
+ fullConfig.merge(cache.entries.get(HostEntry.DEFAULT_NAME));
+ for (final Map.Entry<String, HostEntry> e : cache.entries.entrySet()) {
+ String key = e.getKey();
+ if (isHostMatch(key, hostName)) {
+ fullConfig.merge(e.getValue());
+ }
+ }
+ fullConfig.substitute(hostName, home);
+ h = new Host(fullConfig, hostName, home);
+ cache.hosts.put(hostName, h);
return h;
}
- private synchronized Map<String, Host> refresh() {
+ private synchronized State refresh() {
final long mtime = configFile.lastModified();
if (mtime != lastModified) {
- try {
- final FileInputStream in = new FileInputStream(configFile);
- try {
- hosts = parse(in);
- } finally {
- in.close();
- }
- } catch (FileNotFoundException none) {
- hosts = Collections.emptyMap();
- } catch (IOException err) {
- hosts = Collections.emptyMap();
+ State newState = new State();
+ try (FileInputStream in = new FileInputStream(configFile)) {
+ newState.entries = parse(in);
+ } catch (IOException none) {
+ // Ignore -- we'll set and return an empty state
}
lastModified = mtime;
+ state = newState;
}
- return hosts;
+ return state;
}
- private Map<String, Host> parse(final InputStream in) throws IOException {
- final Map<String, Host> m = new LinkedHashMap<>();
+ private Map<String, HostEntry> parse(final InputStream in)
+ throws IOException {
+ final Map<String, HostEntry> m = new LinkedHashMap<>();
final BufferedReader br = new BufferedReader(new InputStreamReader(in));
- final List<Host> current = new ArrayList<>(4);
+ final List<HostEntry> current = new ArrayList<>(4);
String line;
+ // The man page doesn't say so, but the OpenSSH parser (readconf.c)
+ // starts out in active mode and thus always applies any lines that
+ // occur before the first host block. We gather those options in a
+ // HostEntry for DEFAULT_NAME.
+ HostEntry defaults = new HostEntry();
+ current.add(defaults);
+ m.put(HostEntry.DEFAULT_NAME, defaults);
+
while ((line = br.readLine()) != null) {
line = line.trim();
- if (line.length() == 0 || line.startsWith("#")) //$NON-NLS-1$
+ if (line.isEmpty() || line.startsWith("#")) { //$NON-NLS-1$
continue;
-
- final String[] parts = line.split("[ \t]*[= \t]", 2); //$NON-NLS-1$
- final String keyword = parts[0].trim();
- final String argValue = parts[1].trim();
+ }
+ String[] parts = line.split("[ \t]*[= \t]", 2); //$NON-NLS-1$
+ // Although the ssh-config man page doesn't say so, the OpenSSH
+ // parser does allow quoted keywords.
+ String keyword = dequote(parts[0].trim());
+ // man 5 ssh-config says lines had the format "keyword arguments",
+ // with no indication that arguments were optional. However, let's
+ // not crap out on missing arguments. See bug 444319.
+ String argValue = parts.length > 1 ? parts[1].trim() : ""; //$NON-NLS-1$
if (StringUtils.equalsIgnoreCase("Host", keyword)) { //$NON-NLS-1$
current.clear();
- for (final String pattern : argValue.split("[ \t]")) { //$NON-NLS-1$
- final String name = dequote(pattern);
- Host c = m.get(name);
+ for (String name : HostEntry.parseList(argValue)) {
+ if (name == null || name.isEmpty()) {
+ // null should not occur, but better be safe than sorry.
+ continue;
+ }
+ HostEntry c = m.get(name);
if (c == null) {
- c = new Host();
+ c = new HostEntry();
m.put(name, c);
}
current.add(c);
@@ -206,57 +285,18 @@ private Map<String, Host> parse(final InputStream in) throws IOException {
if (current.isEmpty()) {
// We received an option outside of a Host block. We
// don't know who this should match against, so skip.
- //
continue;
}
- if (StringUtils.equalsIgnoreCase("HostName", keyword)) { //$NON-NLS-1$
- for (final Host c : current)
- if (c.hostName == null)
- c.hostName = dequote(argValue);
- } else if (StringUtils.equalsIgnoreCase("User", keyword)) { //$NON-NLS-1$
- for (final Host c : current)
- if (c.user == null)
- c.user = dequote(argValue);
- } else if (StringUtils.equalsIgnoreCase("Port", keyword)) { //$NON-NLS-1$
- try {
- final int port = Integer.parseInt(dequote(argValue));
- for (final Host c : current)
- if (c.port == 0)
- c.port = port;
- } catch (NumberFormatException nfe) {
- // Bad port number. Don't set it.
+ if (HostEntry.isListKey(keyword)) {
+ List<String> args = HostEntry.parseList(argValue);
+ for (HostEntry entry : current) {
+ entry.setValue(keyword, args);
}
- } else if (StringUtils.equalsIgnoreCase("IdentityFile", keyword)) { //$NON-NLS-1$
- for (final Host c : current)
- if (c.identityFile == null)
- c.identityFile = toFile(dequote(argValue));
- } else if (StringUtils.equalsIgnoreCase(
- "PreferredAuthentications", keyword)) { //$NON-NLS-1$
- for (final Host c : current)
- if (c.preferredAuthentications == null)
- c.preferredAuthentications = nows(dequote(argValue));
- } else if (StringUtils.equalsIgnoreCase("BatchMode", keyword)) { //$NON-NLS-1$
- for (final Host c : current)
- if (c.batchMode == null)
- c.batchMode = yesno(dequote(argValue));
- } else if (StringUtils.equalsIgnoreCase(
- "StrictHostKeyChecking", keyword)) { //$NON-NLS-1$
- String value = dequote(argValue);
- for (final Host c : current)
- if (c.strictHostKeyChecking == null)
- c.strictHostKeyChecking = value;
- } else if (StringUtils.equalsIgnoreCase(
- "ConnectionAttempts", keyword)) { //$NON-NLS-1$
- try {
- final int connectionAttempts = Integer.parseInt(dequote(argValue));
- if (connectionAttempts > 0) {
- for (final Host c : current)
- if (c.connectionAttempts == 0)
- c.connectionAttempts = connectionAttempts;
- }
- } catch (NumberFormatException nfe) {
- // ignore bad values
+ } else if (!argValue.isEmpty()) {
+ argValue = dequote(argValue);
+ for (HostEntry entry : current) {
+ entry.setValue(keyword, argValue);
}
}
}
@@ -264,23 +304,35 @@ private Map<String, Host> parse(final InputStream in) throws IOException {
return m;
}
- private static boolean isHostPattern(final String s) {
- return s.indexOf('*') >= 0 || s.indexOf('?') >= 0;
+ private static boolean isHostMatch(final String pattern,
+ final String name) {
+ if (pattern.startsWith("!")) { //$NON-NLS-1$
+ return !patternMatchesHost(pattern.substring(1), name);
+ } else {
+ return patternMatchesHost(pattern, name);
+ }
}
- private static boolean isHostMatch(final String pattern, final String name) {
- final FileNameMatcher fn;
- try {
- fn = new FileNameMatcher(pattern, null);
- } catch (InvalidPatternException e) {
- return false;
+ private static boolean patternMatchesHost(final String pattern,
+ final String name) {
+ if (pattern.indexOf('*') >= 0 || pattern.indexOf('?') >= 0) {
+ final FileNameMatcher fn;
+ try {
+ fn = new FileNameMatcher(pattern, null);
+ } catch (InvalidPatternException e) {
+ return false;
+ }
+ fn.append(name);
+ return fn.isMatch();
+ } else {
+ // Not a pattern but a full host name
+ return pattern.equals(name);
}
- fn.append(name);
- return fn.isMatch();
}
private static String dequote(final String value) {
- if (value.startsWith("\"") && value.endsWith("\"")) //$NON-NLS-1$ //$NON-NLS-2$
+ if (value.startsWith("\"") && value.endsWith("\"") //$NON-NLS-1$ //$NON-NLS-2$
+ && value.length() > 1)
return value.substring(1, value.length() - 1);
return value;
}
@@ -300,24 +352,453 @@ private static Boolean yesno(final String value) {
return Boolean.FALSE;
}
- private File toFile(final String path) {
- if (path.startsWith("~/")) //$NON-NLS-1$
+ private static File toFile(String path, File home) {
+ if (path.startsWith("~/")) { //$NON-NLS-1$
return new File(home, path.substring(2));
+ }
File ret = new File(path);
- if (ret.isAbsolute())
+ if (ret.isAbsolute()) {
return ret;
+ }
return new File(home, path);
}
+ private static int positive(final String value) {
+ if (value != null) {
+ try {
+ return Integer.parseUnsignedInt(value);
+ } catch (NumberFormatException e) {
+ // Ignore
+ }
+ }
+ return -1;
+ }
+
static String userName() {
return AccessController.doPrivileged(new PrivilegedAction<String>() {
@Override
public String run() {
- return System.getProperty("user.name"); //$NON-NLS-1$
+ return SystemReader.getInstance()
+ .getProperty(Constants.OS_USER_NAME_KEY);
}
});
}
+ private static class HostEntry implements ConfigRepository.Config {
+
+ /**
+ * "Host name" of the HostEntry for the default options before the first
+ * host block in a config file.
+ */
+ public static final String DEFAULT_NAME = ""; //$NON-NLS-1$
+
+ // See com.jcraft.jsch.OpenSSHConfig. Translates some command-line keys
+ // to ssh-config keys.
+ private static final Map<String, String> KEY_MAP = new HashMap<>();
+
+ static {
+ KEY_MAP.put("kex", "KexAlgorithms"); //$NON-NLS-1$//$NON-NLS-2$
+ KEY_MAP.put("server_host_key", "HostKeyAlgorithms"); //$NON-NLS-1$ //$NON-NLS-2$
+ KEY_MAP.put("cipher.c2s", "Ciphers"); //$NON-NLS-1$ //$NON-NLS-2$
+ KEY_MAP.put("cipher.s2c", "Ciphers"); //$NON-NLS-1$ //$NON-NLS-2$
+ KEY_MAP.put("mac.c2s", "Macs"); //$NON-NLS-1$ //$NON-NLS-2$
+ KEY_MAP.put("mac.s2c", "Macs"); //$NON-NLS-1$ //$NON-NLS-2$
+ KEY_MAP.put("compression.s2c", "Compression"); //$NON-NLS-1$ //$NON-NLS-2$
+ KEY_MAP.put("compression.c2s", "Compression"); //$NON-NLS-1$ //$NON-NLS-2$
+ KEY_MAP.put("compression_level", "CompressionLevel"); //$NON-NLS-1$ //$NON-NLS-2$
+ KEY_MAP.put("MaxAuthTries", "NumberOfPasswordPrompts"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Keys that can be specified multiple times, building up a list. (I.e.,
+ * those are the keys that do not follow the general rule of "first
+ * occurrence wins".)
+ */
+ private static final Set<String> MULTI_KEYS = new HashSet<>();
+
+ static {
+ MULTI_KEYS.add("CERTIFICATEFILE"); //$NON-NLS-1$
+ MULTI_KEYS.add("IDENTITYFILE"); //$NON-NLS-1$
+ MULTI_KEYS.add("LOCALFORWARD"); //$NON-NLS-1$
+ MULTI_KEYS.add("REMOTEFORWARD"); //$NON-NLS-1$
+ MULTI_KEYS.add("SENDENV"); //$NON-NLS-1$
+ }
+
+ /**
+ * Keys that take a whitespace-separated list of elements as argument.
+ * Because the dequote-handling is different, we must handle those in
+ * the parser. There are a few other keys that take comma-separated
+ * lists as arguments, but for the parser those are single arguments
+ * that must be quoted if they contain whitespace, and taking them apart
+ * is the responsibility of the user of those keys.
+ */
+ private static final Set<String> LIST_KEYS = new HashSet<>();
+
+ static {
+ LIST_KEYS.add("CANONICALDOMAINS"); //$NON-NLS-1$
+ LIST_KEYS.add("GLOBALKNOWNHOSTSFILE"); //$NON-NLS-1$
+ LIST_KEYS.add("SENDENV"); //$NON-NLS-1$
+ LIST_KEYS.add("USERKNOWNHOSTSFILE"); //$NON-NLS-1$
+ }
+
+ private Map<String, String> options;
+
+ private Map<String, List<String>> multiOptions;
+
+ private Map<String, List<String>> listOptions;
+
+ @Override
+ public String getHostname() {
+ return getValue("HOSTNAME"); //$NON-NLS-1$
+ }
+
+ @Override
+ public String getUser() {
+ return getValue("USER"); //$NON-NLS-1$
+ }
+
+ @Override
+ public int getPort() {
+ return positive(getValue("PORT")); //$NON-NLS-1$
+ }
+
+ private static String mapKey(String key) {
+ String k = KEY_MAP.get(key);
+ if (k == null) {
+ k = key;
+ }
+ return k.toUpperCase(Locale.ROOT);
+ }
+
+ private String findValue(String key) {
+ String k = mapKey(key);
+ String result = options != null ? options.get(k) : null;
+ if (result == null) {
+ // Also check the list and multi options. Modern OpenSSH treats
+ // UserKnownHostsFile and GlobalKnownHostsFile as list-valued,
+ // and so does this parser. Jsch 0.1.54 in general doesn't know
+ // about list-valued options (it _does_ know multi-valued
+ // options, though), and will ask for a single value for such
+ // options.
+ //
+ // Let's be lenient and return at least the first value from
+ // a list-valued or multi-valued key for which Jsch asks for a
+ // single value.
+ List<String> values = listOptions != null ? listOptions.get(k)
+ : null;
+ if (values == null) {
+ values = multiOptions != null ? multiOptions.get(k) : null;
+ }
+ if (values != null && !values.isEmpty()) {
+ result = values.get(0);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public String getValue(String key) {
+ // See com.jcraft.jsch.OpenSSHConfig.MyConfig.getValue() for this
+ // special case.
+ if (key.equals("compression.s2c") //$NON-NLS-1$
+ || key.equals("compression.c2s")) { //$NON-NLS-1$
+ String foo = findValue(key);
+ if (foo == null || foo.equals("no")) { //$NON-NLS-1$
+ return "none,zlib@openssh.com,zlib"; //$NON-NLS-1$
+ }
+ return "zlib@openssh.com,zlib,none"; //$NON-NLS-1$
+ }
+ return findValue(key);
+ }
+
+ @Override
+ public String[] getValues(String key) {
+ String k = mapKey(key);
+ List<String> values = listOptions != null ? listOptions.get(k)
+ : null;
+ if (values == null) {
+ values = multiOptions != null ? multiOptions.get(k) : null;
+ }
+ if (values == null || values.isEmpty()) {
+ return new String[0];
+ }
+ return values.toArray(new String[values.size()]);
+ }
+
+ public void setValue(String key, String value) {
+ String k = key.toUpperCase(Locale.ROOT);
+ if (MULTI_KEYS.contains(k)) {
+ if (multiOptions == null) {
+ multiOptions = new HashMap<>();
+ }
+ List<String> values = multiOptions.get(k);
+ if (values == null) {
+ values = new ArrayList<>(4);
+ multiOptions.put(k, values);
+ }
+ values.add(value);
+ } else {
+ if (options == null) {
+ options = new HashMap<>();
+ }
+ if (!options.containsKey(k)) {
+ options.put(k, value);
+ }
+ }
+ }
+
+ public void setValue(String key, List<String> values) {
+ if (values.isEmpty()) {
+ // Can occur only on a missing argument: ignore.
+ return;
+ }
+ String k = key.toUpperCase(Locale.ROOT);
+ // Check multi-valued keys first; because of the replacement
+ // strategy, they must take precedence over list-valued keys
+ // which always follow the "first occurrence wins" strategy.
+ //
+ // Note that SendEnv is a multi-valued list-valued key. (It's
+ // rather immaterial for JGit, though.)
+ if (MULTI_KEYS.contains(k)) {
+ if (multiOptions == null) {
+ multiOptions = new HashMap<>(2 * MULTI_KEYS.size());
+ }
+ List<String> items = multiOptions.get(k);
+ if (items == null) {
+ items = new ArrayList<>(values);
+ multiOptions.put(k, items);
+ } else {
+ items.addAll(values);
+ }
+ } else {
+ if (listOptions == null) {
+ listOptions = new HashMap<>(2 * LIST_KEYS.size());
+ }
+ if (!listOptions.containsKey(k)) {
+ listOptions.put(k, values);
+ }
+ }
+ }
+
+ public static boolean isListKey(String key) {
+ return LIST_KEYS.contains(key.toUpperCase(Locale.ROOT));
+ }
+
+ /**
+ * Splits the argument into a list of whitespace-separated elements.
+ * Elements containing whitespace must be quoted and will be de-quoted.
+ *
+ * @param argument
+ * argument part of the configuration line as read from the
+ * config file
+ * @return a {@link List} of elements, possibly empty and possibly
+ * containing empty elements
+ */
+ public static List<String> parseList(String argument) {
+ List<String> result = new ArrayList<>(4);
+ int start = 0;
+ int length = argument.length();
+ while (start < length) {
+ // Skip whitespace
+ if (Character.isSpaceChar(argument.charAt(start))) {
+ start++;
+ continue;
+ }
+ if (argument.charAt(start) == '"') {
+ int stop = argument.indexOf('"', ++start);
+ if (stop < start) {
+ // No closing double quote: skip
+ break;
+ }
+ result.add(argument.substring(start, stop));
+ start = stop + 1;
+ } else {
+ int stop = start + 1;
+ while (stop < length
+ && !Character.isSpaceChar(argument.charAt(stop))) {
+ stop++;
+ }
+ result.add(argument.substring(start, stop));
+ start = stop + 1;
+ }
+ }
+ return result;
+ }
+
+ protected void merge(HostEntry entry) {
+ if (entry == null) {
+ // Can occur if we could not read the config file
+ return;
+ }
+ if (entry.options != null) {
+ if (options == null) {
+ options = new HashMap<>();
+ }
+ for (Map.Entry<String, String> item : entry.options
+ .entrySet()) {
+ if (!options.containsKey(item.getKey())) {
+ options.put(item.getKey(), item.getValue());
+ }
+ }
+ }
+ if (entry.listOptions != null) {
+ if (listOptions == null) {
+ listOptions = new HashMap<>(2 * LIST_KEYS.size());
+ }
+ for (Map.Entry<String, List<String>> item : entry.listOptions
+ .entrySet()) {
+ if (!listOptions.containsKey(item.getKey())) {
+ listOptions.put(item.getKey(), item.getValue());
+ }
+ }
+
+ }
+ if (entry.multiOptions != null) {
+ if (multiOptions == null) {
+ multiOptions = new HashMap<>(2 * MULTI_KEYS.size());
+ }
+ for (Map.Entry<String, List<String>> item : entry.multiOptions
+ .entrySet()) {
+ List<String> values = multiOptions.get(item.getKey());
+ if (values == null) {
+ values = new ArrayList<>(item.getValue());
+ multiOptions.put(item.getKey(), values);
+ } else {
+ values.addAll(item.getValue());
+ }
+ }
+ }
+ }
+
+ private class Replacer {
+ private final Map<Character, String> replacements = new HashMap<>();
+
+ public Replacer(String originalHostName, File home) {
+ replacements.put(Character.valueOf('%'), "%"); //$NON-NLS-1$
+ replacements.put(Character.valueOf('d'), home.getPath());
+ // Needs special treatment...
+ String host = getValue("HOSTNAME"); //$NON-NLS-1$
+ replacements.put(Character.valueOf('h'), originalHostName);
+ if (host != null && host.indexOf('%') >= 0) {
+ host = substitute(host, "h"); //$NON-NLS-1$
+ options.put("HOSTNAME", host); //$NON-NLS-1$
+ }
+ if (host != null) {
+ replacements.put(Character.valueOf('h'), host);
+ }
+ String localhost = SystemReader.getInstance().getHostname();
+ replacements.put(Character.valueOf('l'), localhost);
+ int period = localhost.indexOf('.');
+ if (period > 0) {
+ localhost = localhost.substring(0, period);
+ }
+ replacements.put(Character.valueOf('L'), localhost);
+ replacements.put(Character.valueOf('n'), originalHostName);
+ replacements.put(Character.valueOf('p'), getValue("PORT")); //$NON-NLS-1$
+ replacements.put(Character.valueOf('r'), getValue("USER")); //$NON-NLS-1$
+ replacements.put(Character.valueOf('u'), userName());
+ replacements.put(Character.valueOf('C'),
+ substitute("%l%h%p%r", "hlpr")); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ public String substitute(String input, String allowed) {
+ if (input == null || input.length() <= 1
+ || input.indexOf('%') < 0) {
+ return input;
+ }
+ StringBuilder builder = new StringBuilder();
+ int start = 0;
+ int length = input.length();
+ while (start < length) {
+ int percent = input.indexOf('%', start);
+ if (percent < 0 || percent + 1 >= length) {
+ builder.append(input.substring(start));
+ break;
+ }
+ String replacement = null;
+ char ch = input.charAt(percent + 1);
+ if (ch == '%' || allowed.indexOf(ch) >= 0) {
+ replacement = replacements.get(Character.valueOf(ch));
+ }
+ if (replacement == null) {
+ builder.append(input.substring(start, percent + 2));
+ } else {
+ builder.append(input.substring(start, percent))
+ .append(replacement);
+ }
+ start = percent + 2;
+ }
+ return builder.toString();
+ }
+ }
+
+ private List<String> substitute(List<String> values, String allowed,
+ Replacer r) {
+ List<String> result = new ArrayList<>(values.size());
+ for (String value : values) {
+ result.add(r.substitute(value, allowed));
+ }
+ return result;
+ }
+
+ private List<String> replaceTilde(List<String> values, File home) {
+ List<String> result = new ArrayList<>(values.size());
+ for (String value : values) {
+ result.add(toFile(value, home).getPath());
+ }
+ return result;
+ }
+
+ protected void substitute(String originalHostName, File home) {
+ Replacer r = new Replacer(originalHostName, home);
+ if (multiOptions != null) {
+ List<String> values = multiOptions.get("IDENTITYFILE"); //$NON-NLS-1$
+ if (values != null) {
+ values = substitute(values, "dhlru", r); //$NON-NLS-1$
+ values = replaceTilde(values, home);
+ multiOptions.put("IDENTITYFILE", values); //$NON-NLS-1$
+ }
+ values = multiOptions.get("CERTIFICATEFILE"); //$NON-NLS-1$
+ if (values != null) {
+ values = substitute(values, "dhlru", r); //$NON-NLS-1$
+ values = replaceTilde(values, home);
+ multiOptions.put("CERTIFICATEFILE", values); //$NON-NLS-1$
+ }
+ }
+ if (listOptions != null) {
+ List<String> values = listOptions.get("GLOBALKNOWNHOSTSFILE"); //$NON-NLS-1$
+ if (values != null) {
+ values = replaceTilde(values, home);
+ listOptions.put("GLOBALKNOWNHOSTSFILE", values); //$NON-NLS-1$
+ }
+ values = listOptions.get("USERKNOWNHOSTSFILE"); //$NON-NLS-1$
+ if (values != null) {
+ values = replaceTilde(values, home);
+ listOptions.put("USERKNOWNHOSTSFILE", values); //$NON-NLS-1$
+ }
+ }
+ if (options != null) {
+ // HOSTNAME already done in Replacer constructor
+ String value = options.get("IDENTITYAGENT"); //$NON-NLS-1$
+ if (value != null) {
+ value = r.substitute(value, "dhlru"); //$NON-NLS-1$
+ value = toFile(value, home).getPath();
+ options.put("IDENTITYAGENT", value); //$NON-NLS-1$
+ }
+ }
+ // Match is not implemented and would need to be done elsewhere
+ // anyway. ControlPath, LocalCommand, ProxyCommand, and
+ // RemoteCommand are not used by Jsch.
+ }
+
+ @Override
+ @SuppressWarnings("nls")
+ public String toString() {
+ return "HostEntry [options=" + options + ", multiOptions="
+ + multiOptions + ", listOptions=" + listOptions + "]";
+ }
+ }
+
/**
* Configuration of one "Host" block in the configuration file.
* <p>
@@ -330,8 +811,6 @@ public String run() {
* already merged into this block.
*/
public static class Host {
- boolean patternsApplied;
-
String hostName;
int port;
@@ -348,23 +827,18 @@ public static class Host {
int connectionAttempts;
- void copyFrom(final Host src) {
- if (hostName == null)
- hostName = src.hostName;
- if (port == 0)
- port = src.port;
- if (identityFile == null)
- identityFile = src.identityFile;
- if (user == null)
- user = src.user;
- if (preferredAuthentications == null)
- preferredAuthentications = src.preferredAuthentications;
- if (batchMode == null)
- batchMode = src.batchMode;
- if (strictHostKeyChecking == null)
- strictHostKeyChecking = src.strictHostKeyChecking;
- if (connectionAttempts == 0)
- connectionAttempts = src.connectionAttempts;
+ private Config config;
+
+ /**
+ * Creates a new uninitialized {@link Host}.
+ */
+ public Host() {
+ // For API backwards compatibility with pre-4.9 JGit
+ }
+
+ Host(Config config, String hostName, File homeDir) {
+ this.config = config;
+ complete(hostName, homeDir);
}
/**
@@ -432,5 +906,78 @@ public boolean isBatchMode() {
public int getConnectionAttempts() {
return connectionAttempts;
}
+
+
+ private void complete(String initialHostName, File homeDir) {
+ // Try to set values from the options.
+ hostName = config.getHostname();
+ user = config.getUser();
+ port = config.getPort();
+ connectionAttempts = positive(
+ config.getValue("ConnectionAttempts")); //$NON-NLS-1$
+ strictHostKeyChecking = config.getValue("StrictHostKeyChecking"); //$NON-NLS-1$
+ String value = config.getValue("BatchMode"); //$NON-NLS-1$
+ if (value != null) {
+ batchMode = yesno(value);
+ }
+ value = config.getValue("PreferredAuthentications"); //$NON-NLS-1$
+ if (value != null) {
+ preferredAuthentications = nows(value);
+ }
+ // Fill in defaults if still not set
+ if (hostName == null) {
+ hostName = initialHostName;
+ }
+ if (user == null) {
+ user = OpenSshConfig.userName();
+ }
+ if (port <= 0) {
+ port = OpenSshConfig.SSH_PORT;
+ }
+ if (connectionAttempts <= 0) {
+ connectionAttempts = 1;
+ }
+ String[] identityFiles = config.getValues("IdentityFile"); //$NON-NLS-1$
+ if (identityFiles != null && identityFiles.length > 0) {
+ identityFile = toFile(identityFiles[0], homeDir);
+ }
+ }
+
+ Config getConfig() {
+ return config;
+ }
+
+ @Override
+ @SuppressWarnings("nls")
+ public String toString() {
+ return "Host [hostName=" + hostName + ", port=" + port
+ + ", identityFile=" + identityFile + ", user=" + user
+ + ", preferredAuthentications=" + preferredAuthentications
+ + ", batchMode=" + batchMode + ", strictHostKeyChecking="
+ + strictHostKeyChecking + ", connectionAttempts="
+ + connectionAttempts + ", config=" + config + "]";
+ }
+ }
+
+ /**
+ * Retrieves the full {@link com.jcraft.jsch.ConfigRepository.Config Config}
+ * for the given host name. Should be called only by Jsch and tests.
+ *
+ * @param hostName
+ * to get the config for
+ * @return the configuration for the host
+ * @since 4.9
+ */
+ @Override
+ public Config getConfig(String hostName) {
+ Host host = lookup(hostName);
+ return host.getConfig();
+ }
+
+ @Override
+ @SuppressWarnings("nls")
+ public String toString() {
+ return "OpenSshConfig [home=" + home + ", configFile=" + configFile
+ + ", lastModified=" + lastModified + ", state=" + state + "]";
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
index c82b389..833d211 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
@@ -66,6 +66,7 @@
import org.eclipse.jgit.internal.storage.pack.BinaryDelta;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.BatchingProgressMonitor;
+import org.eclipse.jgit.lib.BlobObjectChecker;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.InflaterCache;
import org.eclipse.jgit.lib.MutableObjectId;
@@ -82,6 +83,7 @@
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.util.BlockList;
import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.LongMap;
import org.eclipse.jgit.util.NB;
import org.eclipse.jgit.util.sha1.SHA1;
@@ -143,7 +145,7 @@ public static enum Source {
private boolean expectDataAfterPackFooter;
- private long objectCount;
+ private long expectedObjectCount;
private PackedObjectInfo[] entries;
@@ -173,8 +175,8 @@ public static enum Source {
private LongMap<UnresolvedDelta> baseByPos;
- /** Blobs whose contents need to be double-checked after indexing. */
- private BlockList<PackedObjectInfo> deferredCheckBlobs;
+ /** Objects need to be double-checked for collision after indexing. */
+ private BlockList<PackedObjectInfo> collisionCheckObjs;
private MessageDigest packDigest;
@@ -525,15 +527,15 @@ public PackLock parse(ProgressMonitor receiving, ProgressMonitor resolving)
try {
readPackHeader();
- entries = new PackedObjectInfo[(int) objectCount];
+ entries = new PackedObjectInfo[(int) expectedObjectCount];
baseById = new ObjectIdOwnerMap<>();
baseByPos = new LongMap<>();
- deferredCheckBlobs = new BlockList<>();
+ collisionCheckObjs = new BlockList<>();
receiving.beginTask(JGitText.get().receivingObjects,
- (int) objectCount);
+ (int) expectedObjectCount);
try {
- for (int done = 0; done < objectCount; done++) {
+ for (int done = 0; done < expectedObjectCount; done++) {
indexOneObject();
receiving.update(1);
if (receiving.isCancelled())
@@ -545,32 +547,12 @@ public PackLock parse(ProgressMonitor receiving, ProgressMonitor resolving)
receiving.endTask();
}
- if (!deferredCheckBlobs.isEmpty())
- doDeferredCheckBlobs();
+ if (!collisionCheckObjs.isEmpty()) {
+ checkObjectCollision();
+ }
+
if (deltaCount > 0) {
- if (resolving instanceof BatchingProgressMonitor) {
- ((BatchingProgressMonitor) resolving).setDelayStart(
- 1000,
- TimeUnit.MILLISECONDS);
- }
- resolving.beginTask(JGitText.get().resolvingDeltas, deltaCount);
- resolveDeltas(resolving);
- if (entryCount < objectCount) {
- if (!isAllowThin()) {
- throw new IOException(MessageFormat.format(
- JGitText.get().packHasUnresolvedDeltas,
- Long.valueOf(objectCount - entryCount)));
- }
-
- resolveDeltasWithExternalBases(resolving);
-
- if (entryCount < objectCount) {
- throw new IOException(MessageFormat.format(
- JGitText.get().packHasUnresolvedDeltas,
- Long.valueOf(objectCount - entryCount)));
- }
- }
- resolving.endTask();
+ processDeltas(resolving);
}
packDigest = null;
@@ -593,6 +575,31 @@ public PackLock parse(ProgressMonitor receiving, ProgressMonitor resolving)
return null; // By default there is no locking.
}
+ private void processDeltas(ProgressMonitor resolving) throws IOException {
+ if (resolving instanceof BatchingProgressMonitor) {
+ ((BatchingProgressMonitor) resolving).setDelayStart(1000,
+ TimeUnit.MILLISECONDS);
+ }
+ resolving.beginTask(JGitText.get().resolvingDeltas, deltaCount);
+ resolveDeltas(resolving);
+ if (entryCount < expectedObjectCount) {
+ if (!isAllowThin()) {
+ throw new IOException(MessageFormat.format(
+ JGitText.get().packHasUnresolvedDeltas,
+ Long.valueOf(expectedObjectCount - entryCount)));
+ }
+
+ resolveDeltasWithExternalBases(resolving);
+
+ if (entryCount < expectedObjectCount) {
+ throw new IOException(MessageFormat.format(
+ JGitText.get().packHasUnresolvedDeltas,
+ Long.valueOf(expectedObjectCount - entryCount)));
+ }
+ }
+ resolving.endTask();
+ }
+
private void resolveDeltas(final ProgressMonitor progress)
throws IOException {
final int last = entryCount;
@@ -675,10 +682,14 @@ private void resolveDeltas(DeltaVisit visit, final int type,
objectDigest.digest(tempObjectId);
verifySafeObject(tempObjectId, type, visit.data);
+ if (isCheckObjectCollisions() && readCurs.has(tempObjectId)) {
+ checkObjectCollision(tempObjectId, type, visit.data);
+ }
PackedObjectInfo oe;
oe = newInfo(tempObjectId, visit.delta, visit.parent.id);
oe.setOffset(visit.delta.position);
+ oe.setType(type);
onInflatedObjectData(oe, type, visit.data);
addObjectAndTrack(oe);
visit.id = oe;
@@ -849,10 +860,9 @@ private void resolveDeltasWithExternalBases(final ProgressMonitor progress)
visit.id = baseId;
final int typeCode = ldr.getType();
final PackedObjectInfo oe = newInfo(baseId, null, null);
-
+ oe.setType(typeCode);
if (onAppendBase(typeCode, visit.data, oe))
entries[entryCount++] = oe;
-
visit.nextChild = firstChildOf(oe);
resolveDeltas(visit.next(), typeCode,
new ObjectTypeAndSize(), progress);
@@ -873,7 +883,7 @@ private void resolveDeltasWithExternalBases(final ProgressMonitor progress)
private void growEntries(int extraObjects) {
final PackedObjectInfo[] ne;
- ne = new PackedObjectInfo[(int) objectCount + extraObjects];
+ ne = new PackedObjectInfo[(int) expectedObjectCount + extraObjects];
System.arraycopy(entries, 0, ne, 0, entryCount);
entries = ne;
}
@@ -896,9 +906,9 @@ private void readPackHeader() throws IOException {
if (vers != 2 && vers != 3)
throw new IOException(MessageFormat.format(
JGitText.get().unsupportedPackVersion, Long.valueOf(vers)));
- objectCount = NB.decodeUInt32(buf, p + 8);
+ final long objectCount = NB.decodeUInt32(buf, p + 8);
use(hdrln);
-
+ setExpectedObjectCount(objectCount);
onPackHeader(objectCount);
}
@@ -1031,24 +1041,29 @@ private void whole(final long pos, final int type, final long sz)
objectDigest.update((byte) 0);
final byte[] data;
- boolean checkContentLater = false;
if (type == Constants.OBJ_BLOB) {
byte[] readBuffer = buffer();
InputStream inf = inflate(Source.INPUT, sz);
+ BlobObjectChecker checker = null;
+ if (objCheck != null) {
+ checker = objCheck.newBlobObjectChecker();
+ }
+ if (checker == null) {
+ checker = BlobObjectChecker.NULL_CHECKER;
+ }
long cnt = 0;
while (cnt < sz) {
int r = inf.read(readBuffer);
if (r <= 0)
break;
objectDigest.update(readBuffer, 0, r);
+ checker.update(readBuffer, 0, r);
cnt += r;
}
inf.close();
objectDigest.digest(tempObjectId);
- checkContentLater = isCheckObjectCollisions()
- && readCurs.has(tempObjectId);
+ checker.endBlob(tempObjectId);
data = null;
-
} else {
data = inflateAndReturn(Source.INPUT, sz);
objectDigest.update(data);
@@ -1058,16 +1073,32 @@ private void whole(final long pos, final int type, final long sz)
PackedObjectInfo obj = newInfo(tempObjectId, null, null);
obj.setOffset(pos);
+ obj.setType(type);
onEndWholeObject(obj);
if (data != null)
onInflatedObjectData(obj, type, data);
addObjectAndTrack(obj);
- if (checkContentLater)
- deferredCheckBlobs.add(obj);
+
+ if (isCheckObjectCollisions()) {
+ collisionCheckObjs.add(obj);
+ }
}
- private void verifySafeObject(final AnyObjectId id, final int type,
- final byte[] data) throws IOException {
+ /**
+ * Verify the integrity of the object.
+ *
+ * @param id
+ * identity of the object to be checked.
+ * @param type
+ * the type of the object.
+ * @param data
+ * raw content of the object.
+ * @throws CorruptObjectException
+ * @since 4.9
+ *
+ */
+ protected void verifySafeObject(final AnyObjectId id, final int type,
+ final byte[] data) throws CorruptObjectException {
if (objCheck != null) {
try {
objCheck.check(id, type, data);
@@ -1075,65 +1106,73 @@ private void verifySafeObject(final AnyObjectId id, final int type,
if (e.getErrorType() != null) {
throw e;
}
- throw new CorruptObjectException(MessageFormat.format(
- JGitText.get().invalidObject,
- Constants.typeString(type),
- readCurs.abbreviate(id, 10).name(),
- e.getMessage()), e);
- }
- }
-
- if (isCheckObjectCollisions()) {
- try {
- final ObjectLoader ldr = readCurs.open(id, type);
- final byte[] existingData = ldr.getCachedBytes(data.length);
- if (!Arrays.equals(data, existingData)) {
- throw new IOException(MessageFormat.format(
- JGitText.get().collisionOn, id.name()));
- }
- } catch (MissingObjectException notLocal) {
- // This is OK, we don't have a copy of the object locally
- // but the API throws when we try to read it as usually its
- // an error to read something that doesn't exist.
+ throw new CorruptObjectException(
+ MessageFormat.format(JGitText.get().invalidObject,
+ Constants.typeString(type), id.name(),
+ e.getMessage()),
+ e);
}
}
}
- private void doDeferredCheckBlobs() throws IOException {
+ private void checkObjectCollision() throws IOException {
+ for (PackedObjectInfo obj : collisionCheckObjs) {
+ if (!readCurs.has(obj)) {
+ continue;
+ }
+ checkObjectCollision(obj);
+ }
+ }
+
+ private void checkObjectCollision(PackedObjectInfo obj)
+ throws IOException {
+ ObjectTypeAndSize info = openDatabase(obj, new ObjectTypeAndSize());
final byte[] readBuffer = buffer();
final byte[] curBuffer = new byte[readBuffer.length];
- ObjectTypeAndSize info = new ObjectTypeAndSize();
-
- for (PackedObjectInfo obj : deferredCheckBlobs) {
- info = openDatabase(obj, info);
-
- if (info.type != Constants.OBJ_BLOB)
+ long sz = info.size;
+ InputStream pck = null;
+ try (ObjectStream cur = readCurs.open(obj, info.type).openStream()) {
+ if (cur.getSize() != sz) {
throw new IOException(MessageFormat.format(
- JGitText.get().unknownObjectType,
- Integer.valueOf(info.type)));
-
- ObjectStream cur = readCurs.open(obj, info.type).openStream();
- try {
- long sz = info.size;
- if (cur.getSize() != sz)
- throw new IOException(MessageFormat.format(
- JGitText.get().collisionOn, obj.name()));
- InputStream pck = inflate(Source.DATABASE, sz);
- while (0 < sz) {
- int n = (int) Math.min(readBuffer.length, sz);
- IO.readFully(cur, curBuffer, 0, n);
- IO.readFully(pck, readBuffer, 0, n);
- for (int i = 0; i < n; i++) {
- if (curBuffer[i] != readBuffer[i])
- throw new IOException(MessageFormat.format(JGitText
- .get().collisionOn, obj.name()));
- }
- sz -= n;
- }
- pck.close();
- } finally {
- cur.close();
+ JGitText.get().collisionOn, obj.name()));
}
+ pck = inflate(Source.DATABASE, sz);
+ while (0 < sz) {
+ int n = (int) Math.min(readBuffer.length, sz);
+ IO.readFully(cur, curBuffer, 0, n);
+ IO.readFully(pck, readBuffer, 0, n);
+ for (int i = 0; i < n; i++) {
+ if (curBuffer[i] != readBuffer[i]) {
+ throw new IOException(MessageFormat.format(JGitText
+ .get().collisionOn, obj.name()));
+ }
+ }
+ sz -= n;
+ }
+ } catch (MissingObjectException notLocal) {
+ // This is OK, we don't have a copy of the object locally
+ // but the API throws when we try to read it as usually its
+ // an error to read something that doesn't exist.
+ } finally {
+ if (pck != null) {
+ pck.close();
+ }
+ }
+ }
+
+ private void checkObjectCollision(AnyObjectId obj, int type, byte[] data)
+ throws IOException {
+ try {
+ final ObjectLoader ldr = readCurs.open(obj, type);
+ final byte[] existingData = ldr.getCachedBytes(data.length);
+ if (!Arrays.equals(data, existingData)) {
+ throw new IOException(MessageFormat.format(
+ JGitText.get().collisionOn, obj.name()));
+ }
+ } catch (MissingObjectException notLocal) {
+ // This is OK, we don't have a copy of the object locally
+ // but the API throws when we try to read it as usually its
+ // an error to read something that doesn't exist.
}
}
@@ -1250,6 +1289,23 @@ protected PackedObjectInfo newInfo(AnyObjectId id, UnresolvedDelta delta,
}
/**
+ * Set the expected number of objects in the pack stream.
+ * <p>
+ * The object count in the pack header is not always correct for some Dfs
+ * pack files. e.g. INSERT pack always assume 1 object in the header since
+ * the actual object count is unknown when the pack is written.
+ * <p>
+ * If external implementation wants to overwrite the expectedObjectCount,
+ * they should call this method during {@link #onPackHeader(long)}.
+ *
+ * @param expectedObjectCount
+ * @since 4.9
+ */
+ protected void setExpectedObjectCount(long expectedObjectCount) {
+ this.expectedObjectCount = expectedObjectCount;
+ }
+
+ /**
* Store bytes received from the raw stream.
* <p>
* This method is invoked during {@link #parse(ProgressMonitor)} as data is
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java
index 6da1c57..381c228 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java
@@ -45,6 +45,7 @@
package org.eclipse.jgit.transport;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectIdOwnerMap;
/**
@@ -59,6 +60,8 @@ public class PackedObjectInfo extends ObjectIdOwnerMap.Entry {
private int crc;
+ private int type = Constants.OBJ_BAD;
+
PackedObjectInfo(final long headerOffset, final int packedCRC,
final AnyObjectId id) {
super(id);
@@ -112,4 +115,24 @@ public int getCRC() {
public void setCRC(final int crc) {
this.crc = crc;
}
+
+ /**
+ * @return the object type. The default type is OBJ_BAD, which is considered
+ * as unknown or invalid type.
+ * @since 4.9
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * Record the object type if applicable.
+ *
+ * @param type
+ * the object type.
+ * @since 4.9
+ */
+ public void setType(int type) {
+ this.type = type;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java
new file mode 100644
index 0000000..bff9c71
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConfig.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017, David Pursehouse <david.pursehouse@gmail.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.util.StringUtils;
+
+/**
+ * Push section of a Git configuration file.
+ *
+ * @since 4.9
+ */
+public class PushConfig {
+ /**
+ * Config values for push.recurseSubmodules.
+ */
+ public enum PushRecurseSubmodulesMode implements Config.ConfigEnum {
+ /**
+ * Verify that all submodule commits that changed in the revisions to be
+ * pushed are available on at least one remote of the submodule.
+ */
+ CHECK("check"), //$NON-NLS-1$
+
+ /**
+ * All submodules that changed in the revisions to be pushed will be
+ * pushed.
+ */
+ ON_DEMAND("on-demand"), //$NON-NLS-1$
+
+ /** Default behavior of ignoring submodules when pushing is retained. */
+ NO("false"); //$NON-NLS-1$
+
+ private final String configValue;
+
+ private PushRecurseSubmodulesMode(String configValue) {
+ this.configValue = configValue;
+ }
+
+ @Override
+ public String toConfigValue() {
+ return configValue;
+ }
+
+ @Override
+ public boolean matchConfigValue(String s) {
+ if (StringUtils.isEmptyOrNull(s)) {
+ return false;
+ }
+ s = s.replace('-', '_');
+ return name().equalsIgnoreCase(s)
+ || configValue.equalsIgnoreCase(s);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
index 2b21c4a..e9681b3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
@@ -52,6 +52,7 @@
import java.util.Collection;
import java.util.List;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
@@ -190,6 +191,20 @@ public static void abort(Iterable<ReceiveCommand> commands) {
}
}
+ /**
+ * Check whether a command failed due to transaction aborted.
+ *
+ * @param cmd
+ * command.
+ * @return whether the command failed due to transaction aborted, as in {@link
+ * #abort(Iterable)}.
+ * @since 4.9
+ */
+ public static boolean isTransactionAborted(ReceiveCommand cmd) {
+ return cmd.getResult() == REJECTED_OTHER_REASON
+ && cmd.getMessage().equals(JGitText.get().transactionAborted);
+ }
+
private final ObjectId oldId;
private final ObjectId newId;
@@ -204,13 +219,21 @@ public static void abort(Iterable<ReceiveCommand> commands) {
private String message;
+ private boolean customRefLog;
+
+ private String refLogMessage;
+
+ private boolean refLogIncludeResult;
+
+ private Boolean forceRefLog;
+
private boolean typeIsCorrect;
/**
* Create a new command for {@link BaseReceivePack}.
*
* @param oldId
- * the old object id; must not be null. Use
+ * the expected old object id; must not be null. Use
* {@link ObjectId#zeroId()} to indicate a ref creation.
* @param newId
* the new object id; must not be null. Use
@@ -220,15 +243,23 @@ public static void abort(Iterable<ReceiveCommand> commands) {
*/
public ReceiveCommand(final ObjectId oldId, final ObjectId newId,
final String name) {
+ if (oldId == null) {
+ throw new IllegalArgumentException(JGitText.get().oldIdMustNotBeNull);
+ }
+ if (newId == null) {
+ throw new IllegalArgumentException(JGitText.get().newIdMustNotBeNull);
+ }
this.oldId = oldId;
this.newId = newId;
this.name = name;
type = Type.UPDATE;
- if (ObjectId.zeroId().equals(oldId))
+ if (ObjectId.zeroId().equals(oldId)) {
type = Type.CREATE;
- if (ObjectId.zeroId().equals(newId))
+ }
+ if (ObjectId.zeroId().equals(newId)) {
type = Type.DELETE;
+ }
}
/**
@@ -243,14 +274,45 @@ public ReceiveCommand(final ObjectId oldId, final ObjectId newId,
* @param name
* name of the ref being affected.
* @param type
- * type of the command.
+ * type of the command. Must be {@link Type#CREATE} if {@code
+ * oldId} is zero, or {@link Type#DELETE} if {@code newId} is zero.
* @since 2.0
*/
public ReceiveCommand(final ObjectId oldId, final ObjectId newId,
final String name, final Type type) {
+ if (oldId == null) {
+ throw new IllegalArgumentException(JGitText.get().oldIdMustNotBeNull);
+ }
+ if (newId == null) {
+ throw new IllegalArgumentException(JGitText.get().newIdMustNotBeNull);
+ }
this.oldId = oldId;
this.newId = newId;
this.name = name;
+ switch (type) {
+ case CREATE:
+ if (!ObjectId.zeroId().equals(oldId)) {
+ throw new IllegalArgumentException(
+ JGitText.get().createRequiresZeroOldId);
+ }
+ break;
+ case DELETE:
+ if (!ObjectId.zeroId().equals(newId)) {
+ throw new IllegalArgumentException(
+ JGitText.get().deleteRequiresZeroNewId);
+ }
+ break;
+ case UPDATE:
+ case UPDATE_NONFASTFORWARD:
+ if (ObjectId.zeroId().equals(newId)
+ || ObjectId.zeroId().equals(oldId)) {
+ throw new IllegalArgumentException(
+ JGitText.get().updateRequiresOldIdAndNewId);
+ }
+ break;
+ default:
+ throw new IllegalStateException(JGitText.get().enumValueNotSupported0);
+ }
this.type = type;
}
@@ -290,6 +352,116 @@ public String getMessage() {
}
/**
+ * Set the message to include in the reflog.
+ * <p>
+ * Overrides the default set by {@code setRefLogMessage} on any containing
+ * {@link org.eclipse.jgit.lib.BatchRefUpdate}.
+ *
+ * @param msg
+ * the message to describe this change. If null and appendStatus is
+ * false, the reflog will not be updated.
+ * @param appendStatus
+ * true if the status of the ref change (fast-forward or
+ * forced-update) should be appended to the user supplied message.
+ * @since 4.9
+ */
+ public void setRefLogMessage(String msg, boolean appendStatus) {
+ customRefLog = true;
+ if (msg == null && !appendStatus) {
+ disableRefLog();
+ } else if (msg == null && appendStatus) {
+ refLogMessage = ""; //$NON-NLS-1$
+ refLogIncludeResult = true;
+ } else {
+ refLogMessage = msg;
+ refLogIncludeResult = appendStatus;
+ }
+ }
+
+ /**
+ * Don't record this update in the ref's associated reflog.
+ * <p>
+ * Equivalent to {@code setRefLogMessage(null, false)}.
+ *
+ * @since 4.9
+ */
+ public void disableRefLog() {
+ customRefLog = true;
+ refLogMessage = null;
+ refLogIncludeResult = false;
+ }
+
+ /**
+ * Force writing a reflog for the updated ref.
+ *
+ * @param force whether to force.
+ * @since 4.9
+ */
+ public void setForceRefLog(boolean force) {
+ forceRefLog = Boolean.valueOf(force);
+ }
+
+ /**
+ * Check whether this command has a custom reflog message setting that should
+ * override defaults in any containing
+ * {@link org.eclipse.jgit.lib.BatchRefUpdate}.
+ * <p>
+ * Does not take into account whether {@code #setForceRefLog(boolean)} has
+ * been called.
+ *
+ * @return whether a custom reflog is set.
+ * @since 4.9
+ */
+ public boolean hasCustomRefLog() {
+ return customRefLog;
+ }
+
+ /**
+ * Check whether log has been disabled by {@link #disableRefLog()}.
+ *
+ * @return true if disabled.
+ * @since 4.9
+ */
+ public boolean isRefLogDisabled() {
+ return refLogMessage == null;
+ }
+
+ /**
+ * Get the message to include in the reflog.
+ *
+ * @return message the caller wants to include in the reflog; null if the
+ * update should not be logged.
+ * @since 4.9
+ */
+ @Nullable
+ public String getRefLogMessage() {
+ return refLogMessage;
+ }
+
+ /**
+ * Check whether the reflog message should include the result of the update,
+ * such as fast-forward or force-update.
+ *
+ * @return true if the message should include the result.
+ * @since 4.9
+ */
+ public boolean isRefLogIncludingResult() {
+ return refLogIncludeResult;
+ }
+
+ /**
+ * Check whether the reflog should be written regardless of repo defaults.
+ *
+ * @return whether force writing is enabled; null if {@code
+ * #setForceRefLog(boolean)} was never called.
+ * @since 4.9
+ */
+ @Nullable
+ public Boolean isForceRefLog() {
+ return forceRefLog;
+ }
+
+ /**
* Set the status of this command.
*
* @param s
@@ -355,6 +527,7 @@ public void execute(final BaseReceivePack rp) {
try {
final RefUpdate ru = rp.getRepository().updateRef(getRefName());
ru.setRefLogIdent(rp.getRefLogIdent());
+ ru.setRefLogMessage(refLogMessage, refLogIncludeResult);
switch (getType()) {
case DELETE:
if (!ObjectId.zeroId().equals(getOldId())) {
@@ -428,6 +601,14 @@ public void setResult(RefUpdate.Result r) {
setResult(Result.REJECTED_CURRENT_BRANCH);
break;
+ case REJECTED_MISSING_OBJECT:
+ setResult(Result.REJECTED_MISSING_OBJECT);
+ break;
+
+ case REJECTED_OTHER_REASON:
+ setResult(Result.REJECTED_OTHER_REASON);
+ break;
+
default:
setResult(Result.REJECTED_OTHER_REASON, r.name());
break;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java
index d91684e..c968ba3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java
@@ -170,38 +170,49 @@ public RemoteConfig(final Config rc, final String remoteName)
vlst = rc.getStringList(SECTION, name, KEY_URL);
Map<String, String> insteadOf = getReplacements(rc, KEY_INSTEADOF);
uris = new ArrayList<>(vlst.length);
- for (final String s : vlst)
+ for (final String s : vlst) {
uris.add(new URIish(replaceUri(s, insteadOf)));
-
- Map<String, String> pushInsteadOf = getReplacements(rc,
- KEY_PUSHINSTEADOF);
- vlst = rc.getStringList(SECTION, name, KEY_PUSHURL);
- pushURIs = new ArrayList<>(vlst.length);
- for (final String s : vlst)
- pushURIs.add(new URIish(replaceUri(s, pushInsteadOf)));
-
- vlst = rc.getStringList(SECTION, name, KEY_FETCH);
- fetch = new ArrayList<>(vlst.length);
- for (final String s : vlst)
- fetch.add(new RefSpec(s));
-
- vlst = rc.getStringList(SECTION, name, KEY_PUSH);
- push = new ArrayList<>(vlst.length);
- for (final String s : vlst)
- push.add(new RefSpec(s));
-
+ }
+ String[] plst = rc.getStringList(SECTION, name, KEY_PUSHURL);
+ pushURIs = new ArrayList<>(plst.length);
+ for (final String s : plst) {
+ pushURIs.add(new URIish(s));
+ }
+ if (pushURIs.isEmpty()) {
+ // Would default to the uris. If we have pushinsteadof, we must
+ // supply rewritten push uris.
+ Map<String, String> pushInsteadOf = getReplacements(rc,
+ KEY_PUSHINSTEADOF);
+ if (!pushInsteadOf.isEmpty()) {
+ for (String s : vlst) {
+ String replaced = replaceUri(s, pushInsteadOf);
+ if (!s.equals(replaced)) {
+ pushURIs.add(new URIish(replaced));
+ }
+ }
+ }
+ }
+ fetch = rc.getRefSpecs(SECTION, name, KEY_FETCH);
+ push = rc.getRefSpecs(SECTION, name, KEY_PUSH);
val = rc.getString(SECTION, name, KEY_UPLOADPACK);
- if (val == null)
+ if (val == null) {
val = DEFAULT_UPLOAD_PACK;
+ }
uploadpack = val;
val = rc.getString(SECTION, name, KEY_RECEIVEPACK);
- if (val == null)
+ if (val == null) {
val = DEFAULT_RECEIVE_PACK;
+ }
receivepack = val;
- val = rc.getString(SECTION, name, KEY_TAGOPT);
- tagopt = TagOpt.fromOption(val);
+ try {
+ val = rc.getString(SECTION, name, KEY_TAGOPT);
+ tagopt = TagOpt.fromOption(val);
+ } catch (IllegalArgumentException e) {
+ // C git silently ignores invalid tagopt values.
+ tagopt = TagOpt.AUTO_FOLLOW;
+ }
mirror = rc.getBoolean(SECTION, name, KEY_MIRROR, DEFAULT_MIRROR);
timeout = rc.getInt(SECTION, name, KEY_TIMEOUT, 0);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java
index 5a73cf5..d6a2fe6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java
@@ -83,4 +83,4 @@ public interface RemoteSession {
* Disconnect the remote session
*/
public void disconnect();
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SignedPushConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SignedPushConfig.java
index 83b4aca..1ecbed9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SignedPushConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SignedPushConfig.java
@@ -54,12 +54,7 @@
public class SignedPushConfig {
/** Key for {@link Config#get(SectionParser)}. */
public static final SectionParser<SignedPushConfig> KEY =
- new SectionParser<SignedPushConfig>() {
- @Override
- public SignedPushConfig parse(Config cfg) {
- return new SignedPushConfig(cfg);
- }
- };
+ SignedPushConfig::new;
private String certNonceSeed;
private int certNonceSlopLimit;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
index a1aeceb..2d5029a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
@@ -66,7 +66,7 @@ public abstract class SshSessionFactory {
* <p>
* A factory is always available. By default the factory will read from the
* user's <code>$HOME/.ssh</code> and assume OpenSSH compatibility.
- *
+ *
* @return factory the current factory for this JVM.
*/
public static SshSessionFactory getInstance() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java
index 6f17ebf..74865dc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java
@@ -120,7 +120,7 @@ public SshSessionFactory getSshSessionFactory() {
/**
* Get the default SSH session
- *
+ *
* @return a remote session
* @throws TransportException
* in case of error with opening SSH session
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
index 2198b50..099629c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
@@ -69,15 +69,27 @@ public class TransferConfig {
private static final String FSCK = "fsck"; //$NON-NLS-1$
/** Key for {@link Config#get(SectionParser)}. */
- public static final Config.SectionParser<TransferConfig> KEY = new SectionParser<TransferConfig>() {
- @Override
- public TransferConfig parse(final Config cfg) {
- return new TransferConfig(cfg);
- }
- };
+ public static final Config.SectionParser<TransferConfig> KEY =
+ TransferConfig::new;
- enum FsckMode {
- ERROR, WARN, IGNORE;
+ /**
+ * A git configuration value for how to handle a fsck failure of a particular kind.
+ * Used in e.g. fsck.missingEmail.
+ * @since 4.9
+ */
+ public enum FsckMode {
+ /**
+ * Treat it as an error (the default).
+ */
+ ERROR,
+ /**
+ * Issue a warning (in fact, jgit treats this like IGNORE, but git itself does warn).
+ */
+ WARN,
+ /**
+ * Ignore the error.
+ */
+ IGNORE;
}
private final boolean fetchFsck;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
index 9a40f47..b1b910e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
@@ -242,14 +242,7 @@ public Process exec(String command, int timeout)
args.add(getURI().getHost());
args.add(command);
- ProcessBuilder pb = new ProcessBuilder();
- pb.command(args);
-
- File directory = local.getDirectory();
- if (directory != null)
- pb.environment().put(Constants.GIT_DIR_KEY,
- directory.getPath());
-
+ ProcessBuilder pb = createProcess(args);
try {
return pb.start();
} catch (IOException err) {
@@ -257,6 +250,17 @@ public Process exec(String command, int timeout)
}
}
+ private ProcessBuilder createProcess(List<String> args) {
+ ProcessBuilder pb = new ProcessBuilder();
+ pb.command(args);
+ File directory = local != null ? local.getDirectory() : null;
+ if (directory != null) {
+ pb.environment().put(Constants.GIT_DIR_KEY,
+ directory.getPath());
+ }
+ return pb;
+ }
+
@Override
public void disconnect() {
// Nothing to do
@@ -285,7 +289,7 @@ class SshFetchConnection extends BasePackFetchConnection {
} catch (TransportException err) {
close();
throw err;
- } catch (IOException err) {
+ } catch (Throwable err) {
close();
throw new TransportException(uri,
JGitText.get().remoteHungUpUnexpectedly, err);
@@ -341,10 +345,18 @@ class SshPushConnection extends BasePackPushConnection {
init(process.getInputStream(), process.getOutputStream());
} catch (TransportException err) {
- close();
+ try {
+ close();
+ } catch (Exception e) {
+ // ignore
+ }
throw err;
- } catch (IOException err) {
- close();
+ } catch (Throwable err) {
+ try {
+ close();
+ } catch (Exception e) {
+ // ignore
+ }
throw new TransportException(uri,
JGitText.get().remoteHungUpUnexpectedly, err);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
index 26a254d..7c3f738 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -2,6 +2,7 @@
* Copyright (C) 2008-2010, Google Inc.
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* Copyright (C) 2013, Matthias Sohn <matthias.sohn@sap.com>
+ * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -69,7 +70,11 @@
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.ProxySelector;
+import java.net.URISyntaxException;
import java.net.URL;
+import java.security.cert.CertPathBuilderException;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertificateException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -78,35 +83,44 @@
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashSet;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
+import javax.net.ssl.SSLHandshakeException;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.NoRemoteRepositoryException;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.file.RefDirectory;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.transport.HttpAuthMethod.Type;
+import org.eclipse.jgit.transport.HttpConfig.HttpRedirectMode;
import org.eclipse.jgit.transport.http.HttpConnection;
+import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.HttpSupport;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jgit.util.TemporaryBuffer;
import org.eclipse.jgit.util.io.DisabledOutputStream;
import org.eclipse.jgit.util.io.UnionInputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Transport over HTTP and FTP protocols.
@@ -127,6 +141,9 @@
public class TransportHttp extends HttpTransport implements WalkTransport,
PackTransport {
+ private static final Logger LOG = LoggerFactory
+ .getLogger(TransportHttp.class);
+
private static final String SVC_UPLOAD_PACK = "git-upload-pack"; //$NON-NLS-1$
private static final String SVC_RECEIVE_PACK = "git-receive-pack"; //$NON-NLS-1$
@@ -231,33 +248,18 @@ public Transport open(URIish uri, Repository local, String remoteName)
}
};
- private static final Config.SectionParser<HttpConfig> HTTP_KEY = new SectionParser<HttpConfig>() {
- @Override
- public HttpConfig parse(final Config cfg) {
- return new HttpConfig(cfg);
- }
- };
+ /**
+ * The current URI we're talking to. The inherited (final) field
+ * {@link #uri} stores the original URI; {@code currentUri} may be different
+ * after redirects.
+ */
+ private URIish currentUri;
- private static class HttpConfig {
- final int postBuffer;
+ private URL baseUrl;
- final boolean sslVerify;
+ private URL objectsUrl;
- HttpConfig(final Config rc) {
- postBuffer = rc.getInt("http", "postbuffer", 1 * 1024 * 1024); //$NON-NLS-1$ //$NON-NLS-2$
- sslVerify = rc.getBoolean("http", "sslVerify", true); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- HttpConfig() {
- this(new Config());
- }
- }
-
- final URL baseUrl;
-
- private final URL objectsUrl;
-
- final HttpConfig http;
+ private final HttpConfig http;
private final ProxySelector proxySelector;
@@ -267,20 +269,40 @@ private static class HttpConfig {
private Map<String, String> headers;
+ private boolean sslVerify;
+
+ private boolean sslFailure = false;
+
TransportHttp(final Repository local, final URIish uri)
throws NotSupportedException {
super(local, uri);
+ setURI(uri);
+ http = new HttpConfig(local.getConfig(), uri);
+ proxySelector = ProxySelector.getDefault();
+ sslVerify = http.isSslVerify();
+ }
+
+ private URL toURL(URIish urish) throws MalformedURLException {
+ String uriString = urish.toString();
+ if (!uriString.endsWith("/")) { //$NON-NLS-1$
+ uriString += '/';
+ }
+ return new URL(uriString);
+ }
+
+ /**
+ * @param uri
+ * @throws NotSupportedException
+ * @since 4.9
+ */
+ protected void setURI(final URIish uri) throws NotSupportedException {
try {
- String uriString = uri.toString();
- if (!uriString.endsWith("/")) //$NON-NLS-1$
- uriString += "/"; //$NON-NLS-1$
- baseUrl = new URL(uriString);
+ currentUri = uri;
+ baseUrl = toURL(uri);
objectsUrl = new URL(baseUrl, "objects/"); //$NON-NLS-1$
} catch (MalformedURLException e) {
throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
}
- http = local.getConfig().get(HTTP_KEY);
- proxySelector = ProxySelector.getDefault();
}
/**
@@ -291,17 +313,10 @@ private static class HttpConfig {
*/
TransportHttp(final URIish uri) throws NotSupportedException {
super(uri);
- try {
- String uriString = uri.toString();
- if (!uriString.endsWith("/")) //$NON-NLS-1$
- uriString += "/"; //$NON-NLS-1$
- baseUrl = new URL(uriString);
- objectsUrl = new URL(baseUrl, "objects/"); //$NON-NLS-1$
- } catch (MalformedURLException e) {
- throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
- }
- http = new HttpConfig();
+ setURI(uri);
+ http = new HttpConfig(uri);
proxySelector = ProxySelector.getDefault();
+ sslVerify = http.isSslVerify();
}
/**
@@ -469,28 +484,9 @@ public void setAdditionalHeaders(Map<String, String> headers) {
private HttpConnection connect(final String service)
throws TransportException, NotSupportedException {
- final URL u;
- try {
- final StringBuilder b = new StringBuilder();
- b.append(baseUrl);
-
- if (b.charAt(b.length() - 1) != '/')
- b.append('/');
- b.append(Constants.INFO_REFS);
-
- if (useSmartHttp) {
- b.append(b.indexOf("?") < 0 ? '?' : '&'); //$NON-NLS-1$
- b.append("service="); //$NON-NLS-1$
- b.append(service);
- }
-
- u = new URL(b.toString());
- } catch (MalformedURLException e) {
- throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
- }
-
-
+ URL u = getServiceURL(service);
int authAttempts = 1;
+ int redirects = 0;
Collection<Type> ignoreTypes = null;
for (;;) {
try {
@@ -527,9 +523,10 @@ private HttpConnection connect(final String service)
throw new TransportException(uri,
JGitText.get().noCredentialsProvider);
if (authAttempts > 1)
- credentialsProvider.reset(uri);
+ credentialsProvider.reset(currentUri);
if (3 < authAttempts
- || !authMethod.authorize(uri, credentialsProvider)) {
+ || !authMethod.authorize(currentUri,
+ credentialsProvider)) {
throw new TransportException(uri,
JGitText.get().notAuthorized);
}
@@ -538,8 +535,28 @@ private HttpConnection connect(final String service)
case HttpConnection.HTTP_FORBIDDEN:
throw new TransportException(uri, MessageFormat.format(
- JGitText.get().serviceNotPermitted, service));
+ JGitText.get().serviceNotPermitted, baseUrl,
+ service));
+ case HttpConnection.HTTP_MOVED_PERM:
+ case HttpConnection.HTTP_MOVED_TEMP:
+ case HttpConnection.HTTP_SEE_OTHER:
+ case HttpConnection.HTTP_11_MOVED_TEMP:
+ // SEE_OTHER should actually never be sent by a git server,
+ // and in general should occur only on POST requests. But it
+ // doesn't hurt to accept it here as a redirect.
+ if (http.getFollowRedirects() == HttpRedirectMode.FALSE) {
+ throw new TransportException(uri,
+ MessageFormat.format(
+ JGitText.get().redirectsOff,
+ Integer.valueOf(status)));
+ }
+ URIish newUri = redirect(conn.getHeaderField(HDR_LOCATION),
+ Constants.INFO_REFS, redirects++);
+ setURI(newUri);
+ u = getServiceURL(service);
+ authAttempts = 1;
+ break;
default:
String err = status + " " + conn.getResponseMessage(); //$NON-NLS-1$
throw new TransportException(uri, err);
@@ -548,6 +565,9 @@ private HttpConnection connect(final String service)
throw e;
} catch (TransportException e) {
throw e;
+ } catch (SSLHandshakeException e) {
+ handleSslFailure(e);
+ continue; // Re-try
} catch (IOException e) {
if (authMethod.getType() != HttpAuthMethod.Type.NONE) {
if (ignoreTypes == null) {
@@ -568,6 +588,215 @@ private HttpConnection connect(final String service)
}
}
+ private static class CredentialItems {
+ CredentialItem.InformationalMessage message;
+
+ /** Trust the server for this git operation */
+ CredentialItem.YesNoType now;
+
+ /**
+ * Trust the server for all git operations from this repository; may be
+ * {@code null} if the transport was created via
+ * {@link #TransportHttp(URIish)}.
+ */
+ CredentialItem.YesNoType forRepo;
+
+ /** Always trust the server from now on. */
+ CredentialItem.YesNoType always;
+
+ public CredentialItem[] items() {
+ if (forRepo == null) {
+ return new CredentialItem[] { message, now, always };
+ } else {
+ return new CredentialItem[] { message, now, forRepo, always };
+ }
+ }
+ }
+
+ private void handleSslFailure(Throwable e) throws TransportException {
+ if (sslFailure || !trustInsecureSslConnection(e.getCause())) {
+ throw new TransportException(uri,
+ MessageFormat.format(
+ JGitText.get().sslFailureExceptionMessage,
+ currentUri.setPass(null)),
+ e);
+ }
+ sslFailure = true;
+ }
+
+ private boolean trustInsecureSslConnection(Throwable cause) {
+ if (cause instanceof CertificateException
+ || cause instanceof CertPathBuilderException
+ || cause instanceof CertPathValidatorException) {
+ // Certificate expired or revoked, PKIX path building not
+ // possible, self-signed certificate, host does not match ...
+ CredentialsProvider provider = getCredentialsProvider();
+ if (provider != null) {
+ CredentialItems trust = constructSslTrustItems(cause);
+ CredentialItem[] items = trust.items();
+ if (provider.supports(items)) {
+ boolean answered = provider.get(uri, items);
+ if (answered) {
+ // Not canceled
+ boolean trustNow = trust.now.getValue();
+ boolean trustLocal = trust.forRepo != null
+ && trust.forRepo.getValue();
+ boolean trustAlways = trust.always.getValue();
+ if (trustNow || trustLocal || trustAlways) {
+ sslVerify = false;
+ if (trustAlways) {
+ updateSslVerifyUser(false);
+ } else if (trustLocal) {
+ updateSslVerify(local.getConfig(), false);
+ }
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private CredentialItems constructSslTrustItems(Throwable cause) {
+ CredentialItems items = new CredentialItems();
+ String info = MessageFormat.format(JGitText.get().sslFailureInfo,
+ currentUri.setPass(null));
+ String sslMessage = cause.getLocalizedMessage();
+ if (sslMessage == null) {
+ sslMessage = cause.toString();
+ }
+ sslMessage = MessageFormat.format(JGitText.get().sslFailureCause,
+ sslMessage);
+ items.message = new CredentialItem.InformationalMessage(info + '\n'
+ + sslMessage + '\n'
+ + JGitText.get().sslFailureTrustExplanation);
+ items.now = new CredentialItem.YesNoType(JGitText.get().sslTrustNow);
+ if (local != null) {
+ items.forRepo = new CredentialItem.YesNoType(
+ MessageFormat.format(JGitText.get().sslTrustForRepo,
+ local.getDirectory()));
+ }
+ items.always = new CredentialItem.YesNoType(
+ JGitText.get().sslTrustAlways);
+ return items;
+ }
+
+ private void updateSslVerify(StoredConfig config, boolean value) {
+ // Since git uses the original URI for matching, we must also use the
+ // original URI and cannot use the current URI (which might be different
+ // after redirects).
+ String uriPattern = uri.getScheme() + "://" + uri.getHost(); //$NON-NLS-1$
+ int port = uri.getPort();
+ if (port > 0) {
+ uriPattern += ":" + port; //$NON-NLS-1$
+ }
+ config.setBoolean(HttpConfig.HTTP, uriPattern,
+ HttpConfig.SSL_VERIFY_KEY, value);
+ try {
+ config.save();
+ } catch (IOException e) {
+ LOG.error(JGitText.get().sslVerifyCannotSave, e);
+ }
+ }
+
+ private void updateSslVerifyUser(boolean value) {
+ FileBasedConfig userConfig = SystemReader.getInstance()
+ .openUserConfig(null, FS.DETECTED);
+ try {
+ userConfig.load();
+ updateSslVerify(userConfig, value);
+ } catch (IOException | ConfigInvalidException e) {
+ // Log it, but otherwise ignore here.
+ LOG.error(MessageFormat.format(JGitText.get().userConfigFileInvalid,
+ userConfig.getFile().getAbsolutePath(), e));
+ }
+ }
+
+ private URIish redirect(String location, String checkFor, int redirects)
+ throws TransportException {
+ if (location == null || location.isEmpty()) {
+ throw new TransportException(uri,
+ MessageFormat.format(JGitText.get().redirectLocationMissing,
+ baseUrl));
+ }
+ if (redirects >= http.getMaxRedirects()) {
+ throw new TransportException(uri,
+ MessageFormat.format(JGitText.get().redirectLimitExceeded,
+ Integer.valueOf(http.getMaxRedirects()), baseUrl,
+ location));
+ }
+ try {
+ if (!isValidRedirect(baseUrl, location, checkFor)) {
+ throw new TransportException(uri,
+ MessageFormat.format(JGitText.get().redirectBlocked,
+ baseUrl, location));
+ }
+ location = location.substring(0, location.indexOf(checkFor));
+ URIish result = new URIish(location);
+ if (LOG.isInfoEnabled()) {
+ LOG.info(MessageFormat.format(JGitText.get().redirectHttp,
+ uri.setPass(null),
+ Integer.valueOf(redirects), baseUrl, result));
+ }
+ return result;
+ } catch (URISyntaxException e) {
+ throw new TransportException(uri,
+ MessageFormat.format(JGitText.get().invalidRedirectLocation,
+ baseUrl, location),
+ e);
+ }
+ }
+
+ private boolean isValidRedirect(URL current, String next, String checkFor) {
+ // Protocols must be the same, or current is "http" and next "https". We
+ // do not follow redirects from https back to http.
+ String oldProtocol = current.getProtocol().toLowerCase(Locale.ROOT);
+ int schemeEnd = next.indexOf("://"); //$NON-NLS-1$
+ if (schemeEnd < 0) {
+ return false;
+ }
+ String newProtocol = next.substring(0, schemeEnd)
+ .toLowerCase(Locale.ROOT);
+ if (!oldProtocol.equals(newProtocol)) {
+ if (!"https".equals(newProtocol)) { //$NON-NLS-1$
+ return false;
+ }
+ }
+ // git allows only rewriting the root, i.e., everything before INFO_REFS
+ // or the service name
+ if (next.indexOf(checkFor) < 0) {
+ return false;
+ }
+ // Basically we should test here that whatever follows INFO_REFS is
+ // unchanged. But since we re-construct the query part
+ // anyway, it doesn't matter.
+ return true;
+ }
+
+ private URL getServiceURL(final String service)
+ throws NotSupportedException {
+ try {
+ final StringBuilder b = new StringBuilder();
+ b.append(baseUrl);
+
+ if (b.charAt(b.length() - 1) != '/') {
+ b.append('/');
+ }
+ b.append(Constants.INFO_REFS);
+
+ if (useSmartHttp) {
+ b.append(b.indexOf("?") < 0 ? '?' : '&'); //$NON-NLS-1$
+ b.append("service="); //$NON-NLS-1$
+ b.append(service);
+ }
+
+ return new URL(b.toString());
+ } catch (MalformedURLException e) {
+ throw new NotSupportedException(MessageFormat.format(JGitText.get().invalidURL, uri), e);
+ }
+ }
+
/**
* Open an HTTP connection, setting the accept-encoding request header to gzip.
*
@@ -602,10 +831,14 @@ protected HttpConnection httpOpen(String method, URL u,
final Proxy proxy = HttpSupport.proxyFor(proxySelector, u);
HttpConnection conn = connectionFactory.create(u, proxy);
- if (!http.sslVerify && "https".equals(u.getProtocol())) { //$NON-NLS-1$
+ if (!sslVerify && "https".equals(u.getProtocol())) { //$NON-NLS-1$
HttpSupport.disableSslVerify(conn);
}
+ // We must do our own redirect handling to implement git rules and to
+ // handle http->https redirects
+ conn.setInstanceFollowRedirects(false);
+
conn.setRequestMethod(method);
conn.setUseCaches(false);
if (acceptEncoding == AcceptEncoding.GZIP) {
@@ -914,13 +1147,7 @@ abstract class Service {
}
void openStream() throws IOException {
- openStream(null);
- }
-
- void openStream(final String redirectUrl) throws IOException {
- conn = httpOpen(
- METHOD_POST,
- redirectUrl == null ? new URL(baseUrl, serviceName) : new URL(redirectUrl),
+ conn = httpOpen(METHOD_POST, new URL(baseUrl, serviceName),
AcceptEncoding.GZIP);
conn.setInstanceFollowRedirects(false);
conn.setDoOutput(true);
@@ -929,12 +1156,9 @@ void openStream(final String redirectUrl) throws IOException {
}
void sendRequest() throws IOException {
- sendRequest(null);
- }
-
- void sendRequest(final String redirectUrl) throws IOException {
// Try to compress the content, but only if that is smaller.
- TemporaryBuffer buf = new TemporaryBuffer.Heap(http.postBuffer);
+ TemporaryBuffer buf = new TemporaryBuffer.Heap(
+ http.getPostBuffer());
try {
GZIPOutputStream gzip = new GZIPOutputStream(buf);
out.writeTo(gzip, null);
@@ -947,21 +1171,141 @@ void sendRequest(final String redirectUrl) throws IOException {
buf = out;
}
- openStream(redirectUrl);
- if (buf != out)
- conn.setRequestProperty(HDR_CONTENT_ENCODING, ENCODING_GZIP);
- conn.setFixedLengthStreamingMode((int) buf.length());
- final OutputStream httpOut = conn.getOutputStream();
- try {
- buf.writeTo(httpOut, null);
- } finally {
- httpOut.close();
- }
+ HttpAuthMethod authenticator = null;
+ Collection<Type> ignoreTypes = EnumSet.noneOf(Type.class);
+ // Counts number of repeated authentication attempts using the same
+ // authentication scheme
+ int authAttempts = 1;
+ int redirects = 0;
+ for (;;) {
+ try {
+ // The very first time we will try with the authentication
+ // method used on the initial GET request. This is a hint
+ // only; it may fail. If so, we'll then re-try with proper
+ // 401 handling, going through the available authentication
+ // schemes.
+ openStream();
+ if (buf != out) {
+ conn.setRequestProperty(HDR_CONTENT_ENCODING,
+ ENCODING_GZIP);
+ }
+ conn.setFixedLengthStreamingMode((int) buf.length());
+ try (OutputStream httpOut = conn.getOutputStream()) {
+ buf.writeTo(httpOut, null);
+ }
- final int status = HttpSupport.response(conn);
- if (status == HttpConnection.HTTP_MOVED_PERM) {
- String locationHeader = HttpSupport.responseHeader(conn, HDR_LOCATION);
- sendRequest(locationHeader);
+ final int status = HttpSupport.response(conn);
+ switch (status) {
+ case HttpConnection.HTTP_OK:
+ // We're done.
+ return;
+
+ case HttpConnection.HTTP_NOT_FOUND:
+ throw new NoRemoteRepositoryException(uri,
+ MessageFormat.format(JGitText.get().uriNotFound,
+ conn.getURL()));
+
+ case HttpConnection.HTTP_FORBIDDEN:
+ throw new TransportException(uri,
+ MessageFormat.format(
+ JGitText.get().serviceNotPermitted,
+ baseUrl, serviceName));
+
+ case HttpConnection.HTTP_MOVED_PERM:
+ case HttpConnection.HTTP_MOVED_TEMP:
+ case HttpConnection.HTTP_11_MOVED_TEMP:
+ // SEE_OTHER after a POST doesn't make sense for a git
+ // server, so we don't handle it here and thus we'll
+ // report an error in openResponse() later on.
+ if (http.getFollowRedirects() != HttpRedirectMode.TRUE) {
+ // Let openResponse() issue an error
+ return;
+ }
+ currentUri = redirect(conn.getHeaderField(HDR_LOCATION),
+ '/' + serviceName, redirects++);
+ try {
+ baseUrl = toURL(currentUri);
+ } catch (MalformedURLException e) {
+ throw new TransportException(uri,
+ MessageFormat.format(
+ JGitText.get().invalidRedirectLocation,
+ baseUrl, currentUri),
+ e);
+ }
+ continue;
+
+ case HttpConnection.HTTP_UNAUTHORIZED:
+ HttpAuthMethod nextMethod = HttpAuthMethod
+ .scanResponse(conn, ignoreTypes);
+ switch (nextMethod.getType()) {
+ case NONE:
+ throw new TransportException(uri,
+ MessageFormat.format(
+ JGitText.get().authenticationNotSupported,
+ conn.getURL()));
+ case NEGOTIATE:
+ // RFC 4559 states "When using the SPNEGO [...] with
+ // [...] POST, the authentication should be complete
+ // [...] before sending the user data." So in theory
+ // the initial GET should have been authenticated
+ // already. (Unless there was a redirect?)
+ //
+ // We try this only once:
+ ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
+ if (authenticator != null) {
+ ignoreTypes.add(authenticator.getType());
+ }
+ authAttempts = 1;
+ // We only do the Kerberos part of SPNEGO, which
+ // requires only one attempt. We do *not* to the
+ // NTLM part of SPNEGO; it's a multi-round
+ // negotiation and among other problems it would
+ // be unclear when to stop if no HTTP_OK is
+ // forthcoming. In theory a malicious server
+ // could keep sending requests for another NTLM
+ // round, keeping a client stuck here.
+ break;
+ default:
+ // DIGEST or BASIC. Let's be sure we ignore
+ // NEGOTIATE; if it was available, we have tried it
+ // before.
+ ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
+ if (authenticator == null || authenticator
+ .getType() != nextMethod.getType()) {
+ if (authenticator != null) {
+ ignoreTypes.add(authenticator.getType());
+ }
+ authAttempts = 1;
+ }
+ break;
+ }
+ authMethod = nextMethod;
+ authenticator = nextMethod;
+ CredentialsProvider credentialsProvider = getCredentialsProvider();
+ if (credentialsProvider == null) {
+ throw new TransportException(uri,
+ JGitText.get().noCredentialsProvider);
+ }
+ if (authAttempts > 1) {
+ credentialsProvider.reset(currentUri);
+ }
+ if (3 < authAttempts || !authMethod
+ .authorize(currentUri, credentialsProvider)) {
+ throw new TransportException(uri,
+ JGitText.get().notAuthorized);
+ }
+ authAttempts++;
+ continue;
+
+ default:
+ // Just return here; openResponse() will report an
+ // appropriate error.
+ return;
+ }
+ } catch (SSLHandshakeException e) {
+ handleSslFailure(e);
+ continue; // Re-try
+ }
}
}
@@ -1011,7 +1355,7 @@ public long skip(long n) throws IOException {
class HttpOutputStream extends TemporaryBuffer {
HttpOutputStream() {
- super(http.postBuffer);
+ super(http.getPostBuffer());
}
@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
index e4de57a..63236cb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -71,7 +71,6 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -724,7 +723,7 @@ private Map<String, Ref> getAdvertisedOrDefaultRefs() throws IOException {
}
private void service() throws IOException {
- boolean sendPack;
+ boolean sendPack = false;
// If it's a non-bidi request, we need to read the entire request before
// writing a response. Buffer the response until then.
try {
@@ -757,6 +756,17 @@ else if (requestValidator instanceof AnyRequestValidator)
if (!clientShallowCommits.isEmpty())
walk.assumeShallow(clientShallowCommits);
sendPack = negotiate();
+ if (sendPack && !biDirectionalPipe) {
+ // Ensure the request was fully consumed. Any remaining input must
+ // be a protocol error. If we aren't at EOF the implementation is broken.
+ int eof = rawIn.read();
+ if (0 <= eof) {
+ sendPack = false;
+ throw new CorruptObjectException(MessageFormat.format(
+ JGitText.get().expectedEOFReceived,
+ "\\x" + Integer.toHexString(eof))); //$NON-NLS-1$
+ }
+ }
} catch (ServiceMayNotContinueException err) {
if (!err.isOutput() && err.getMessage() != null) {
try {
@@ -783,6 +793,11 @@ else if (requestValidator instanceof AnyRequestValidator)
}
throw err;
} finally {
+ if (!sendPack && !biDirectionalPipe) {
+ while (0 < rawIn.skip(2048) || 0 <= rawIn.read()) {
+ // Discard until EOF.
+ }
+ }
rawOut.stopBuffering();
}
@@ -1241,7 +1256,7 @@ public static final class ReachableCommitRequestValidator
@Override
public void checkWants(UploadPack up, List<ObjectId> wants)
throws PackProtocolException, IOException {
- checkNotAdvertisedWants(up.getRevWalk(), wants,
+ checkNotAdvertisedWants(up, wants,
refIdSet(up.getAdvertisedRefs().values()));
}
}
@@ -1278,7 +1293,7 @@ public static final class ReachableCommitTipRequestValidator
@Override
public void checkWants(UploadPack up, List<ObjectId> wants)
throws PackProtocolException, IOException {
- checkNotAdvertisedWants(up.getRevWalk(), wants,
+ checkNotAdvertisedWants(up, wants,
refIdSet(up.getRepository().getRefDatabase().getRefs(ALL).values()));
}
}
@@ -1296,7 +1311,7 @@ public void checkWants(UploadPack up, List<ObjectId> wants)
}
}
- private static void checkNotAdvertisedWants(RevWalk walk,
+ private static void checkNotAdvertisedWants(UploadPack up,
List<ObjectId> notAdvertisedWants, Set<ObjectId> reachableFrom)
throws MissingObjectException, IncorrectObjectTypeException, IOException {
// Walk the requested commits back to the provided set of commits. If any
@@ -1305,32 +1320,34 @@ private static void checkNotAdvertisedWants(RevWalk walk,
// into an advertised branch it will be marked UNINTERESTING and no commits
// return.
- AsyncRevObjectQueue q = walk.parseAny(notAdvertisedWants, true);
- try {
- RevObject obj;
- while ((obj = q.next()) != null) {
- if (!(obj instanceof RevCommit))
- throw new WantNotValidException(obj);
- walk.markStart((RevCommit) obj);
- }
- } catch (MissingObjectException notFound) {
- throw new WantNotValidException(notFound.getObjectId(), notFound);
- } finally {
- q.release();
- }
- for (ObjectId id : reachableFrom) {
+ try (RevWalk walk = new RevWalk(up.getRevWalk().getObjectReader())) {
+ AsyncRevObjectQueue q = walk.parseAny(notAdvertisedWants, true);
try {
- walk.markUninteresting(walk.parseCommit(id));
- } catch (IncorrectObjectTypeException notCommit) {
- continue;
+ RevObject obj;
+ while ((obj = q.next()) != null) {
+ if (!(obj instanceof RevCommit))
+ throw new WantNotValidException(obj);
+ walk.markStart((RevCommit) obj);
+ }
+ } catch (MissingObjectException notFound) {
+ throw new WantNotValidException(notFound.getObjectId(),
+ notFound);
+ } finally {
+ q.release();
+ }
+ for (ObjectId id : reachableFrom) {
+ try {
+ walk.markUninteresting(walk.parseCommit(id));
+ } catch (IncorrectObjectTypeException notCommit) {
+ continue;
+ }
+ }
+
+ RevCommit bad = walk.next();
+ if (bad != null) {
+ throw new WantNotValidException(bad);
}
}
-
- RevCommit bad = walk.next();
- if (bad != null) {
- throw new WantNotValidException(bad);
- }
- walk.reset();
}
private void addCommonBase(final RevObject o) {
@@ -1386,17 +1403,6 @@ private boolean wantSatisfied(final RevObject want) throws IOException {
private void sendPack() throws IOException {
final boolean sideband = options.contains(OPTION_SIDE_BAND)
|| options.contains(OPTION_SIDE_BAND_64K);
-
- if (!biDirectionalPipe) {
- // Ensure the request was fully consumed. Any remaining input must
- // be a protocol error. If we aren't at EOF the implementation is broken.
- int eof = rawIn.read();
- if (0 <= eof)
- throw new CorruptObjectException(MessageFormat.format(
- JGitText.get().expectedEOFReceived,
- "\\x" + Integer.toHexString(eof))); //$NON-NLS-1$
- }
-
if (sideband) {
try {
sendPack(true);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
index 58081c1..35a1ee1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Christian Halstrick <christian.halstrick@sap.com>
+ * Copyright (C) 2013, 2017 Christian Halstrick <christian.halstrick@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -79,6 +79,26 @@ public interface HttpConnection {
public static final int HTTP_MOVED_PERM = java.net.HttpURLConnection.HTTP_MOVED_PERM;
/**
+ * @see HttpURLConnection#HTTP_MOVED_TEMP
+ * @since 4.9
+ */
+ public static final int HTTP_MOVED_TEMP = java.net.HttpURLConnection.HTTP_MOVED_TEMP;
+
+ /**
+ * @see HttpURLConnection#HTTP_SEE_OTHER
+ * @since 4.9
+ */
+ public static final int HTTP_SEE_OTHER = java.net.HttpURLConnection.HTTP_SEE_OTHER;
+
+ /**
+ * HTTP 1.1 additional MOVED_TEMP status code; value = 307.
+ *
+ * @see #HTTP_MOVED_TEMP
+ * @since 4.9
+ */
+ public static final int HTTP_11_MOVED_TEMP = 307;
+
+ /**
* @see HttpURLConnection#HTTP_NOT_FOUND
*/
public static final int HTTP_NOT_FOUND = java.net.HttpURLConnection.HTTP_NOT_FOUND;
@@ -253,7 +273,7 @@ public void setRequestMethod(String method)
/**
* Configure the connection so that it can be used for https communication.
- *
+ *
* @param km
* the keymanager managing the key material used to authenticate
* the local SSLSocket to its peer
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
index 7654d46..8ab112e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
@@ -244,11 +244,11 @@ private static boolean isUnreasonableName(final String name) {
return true; // no absolute paths
if (name.startsWith("../")) //$NON-NLS-1$
- return true; // no "l../etc/passwd"
+ return true; // no "l../etc/passwd"
if (name.contains("/../")) //$NON-NLS-1$
- return true; // no "foo/../etc/passwd"
+ return true; // no "foo/../etc/passwd"
if (name.contains("/./")) //$NON-NLS-1$
- return true; // "foo/./foo" is insane to ask
+ return true; // "foo/./foo" is insane to ask
if (name.contains("//")) //$NON-NLS-1$
return true; // double slashes is sloppy, don't use it
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java
index 7d2b33f..2b18904 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java
@@ -55,12 +55,8 @@
/** Options used by the {@link WorkingTreeIterator}. */
public class WorkingTreeOptions {
/** Key for {@link Config#get(SectionParser)}. */
- public static final Config.SectionParser<WorkingTreeOptions> KEY = new SectionParser<WorkingTreeOptions>() {
- @Override
- public WorkingTreeOptions parse(final Config cfg) {
- return new WorkingTreeOptions(cfg);
- }
- };
+ public static final Config.SectionParser<WorkingTreeOptions> KEY =
+ WorkingTreeOptions::new;
private final boolean fileMode;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java
index 1719416..2ea8228 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java
@@ -102,4 +102,4 @@ public TreeFilter clone() {
public String toString() {
return "INTERINDEX_DIFF"; //$NON-NLS-1$
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
index 9c2c905..5232d06 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -582,6 +582,10 @@ protected static String readPipe(File dir, String[] command,
}
private static class GobblerThread extends Thread {
+
+ /* The process has 5 seconds to exit after closing stderr */
+ private static final int PROCESS_EXIT_TIMEOUT = 5;
+
private final Process p;
private final String desc;
private final String dir;
@@ -604,15 +608,16 @@ public void run() {
err.append((char) ch);
}
} catch (IOException e) {
- if (p.exitValue() != 0) {
- setError(e, e.getMessage());
+ if (waitForProcessCompletion(e) && p.exitValue() != 0) {
+ setError(e, e.getMessage(), p.exitValue());
fail.set(true);
} else {
// ignore. command terminated faster and stream was just closed
+ // or the process didn't terminate within timeout
}
} finally {
- if (err.length() > 0) {
- setError(null, err.toString());
+ if (waitForProcessCompletion(null) && err.length() > 0) {
+ setError(null, err.toString(), p.exitValue());
if (p.exitValue() != 0) {
fail.set(true);
}
@@ -620,11 +625,27 @@ public void run() {
}
}
- private void setError(IOException e, String message) {
+ @SuppressWarnings("boxing")
+ private boolean waitForProcessCompletion(IOException originalError) {
+ try {
+ if (!p.waitFor(PROCESS_EXIT_TIMEOUT, TimeUnit.SECONDS)) {
+ setError(originalError, MessageFormat.format(
+ JGitText.get().commandClosedStderrButDidntExit,
+ desc, PROCESS_EXIT_TIMEOUT), -1);
+ fail.set(true);
+ }
+ } catch (InterruptedException e) {
+ LOG.error(MessageFormat.format(
+ JGitText.get().threadInterruptedWhileRunning, desc), e);
+ }
+ return false;
+ }
+
+ private void setError(IOException e, String message, int exitCode) {
exception.set(e);
errorMessage.set(MessageFormat.format(
- JGitText.get().exceptionCaughtDuringExcecutionOfCommand,
- desc, dir, Integer.valueOf(p.exitValue()), message));
+ JGitText.get().exceptionCaughtDuringExecutionOfCommand,
+ desc, dir, Integer.valueOf(exitCode), message));
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
index 607e078..d220030 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
@@ -229,7 +229,7 @@ public boolean setExecute(File f, boolean canExecute) {
if (!isFile(f))
return false;
if (!canExecute)
- return f.setExecutable(false);
+ return f.setExecutable(false, false);
try {
Path path = f.toPath();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java
index 658dd06..0a3c846 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java
@@ -71,6 +71,21 @@ public int size() {
}
/**
+ * Check if an entry appears in this collection.
+ *
+ * @param value
+ * the value to search for.
+ * @return true of {@code value} appears in this list.
+ * @since 4.9
+ */
+ public boolean contains(int value) {
+ for (int i = 0; i < count; i++)
+ if (entries[i] == value)
+ return true;
+ return false;
+ }
+
+ /**
* @param i
* index to read, must be in the range [0, {@link #size()}).
* @return the number at the specified index
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/LongMap.java
similarity index 82%
rename from org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/util/LongMap.java
index 4d60202..7b0b0c7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/LongMap.java
@@ -41,15 +41,16 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.eclipse.jgit.transport;
+package org.eclipse.jgit.util;
/**
- * Simple Map<long,Object> helper for {@link PackParser}.
+ * Simple Map<long,Object>.
*
* @param <V>
* type of the value instance.
+ * @since 4.9
*/
-final class LongMap<V> {
+public class LongMap<V> {
private static final float LOAD_FACTOR = 0.75f;
private Node<V>[] table;
@@ -60,16 +61,27 @@ final class LongMap<V> {
/** Next {@link #size} to trigger a {@link #grow()}. */
private int growAt;
- LongMap() {
+ /** Initialize an empty LongMap. */
+ public LongMap() {
table = createArray(64);
growAt = (int) (table.length * LOAD_FACTOR);
}
- boolean containsKey(final long key) {
+ /**
+ * @param key
+ * the key to find.
+ * @return {@code true} if {@code key} is present in the map.
+ */
+ public boolean containsKey(long key) {
return get(key) != null;
}
- V get(final long key) {
+ /**
+ * @param key
+ * the key to find.
+ * @return stored value of the key, or {@code null}.
+ */
+ public V get(long key) {
for (Node<V> n = table[index(key)]; n != null; n = n.next) {
if (n.key == key)
return n.value;
@@ -77,7 +89,12 @@ V get(final long key) {
return null;
}
- V remove(final long key) {
+ /**
+ * @param key
+ * key to remove from the map.
+ * @return old value of the key, or {@code null}.
+ */
+ public V remove(long key) {
Node<V> n = table[index(key)];
Node<V> prior = null;
while (n != null) {
@@ -95,7 +112,14 @@ V remove(final long key) {
return null;
}
- V put(final long key, final V value) {
+ /**
+ * @param key
+ * key to store {@code value} under.
+ * @param value
+ * new value.
+ * @return prior value, or null.
+ */
+ public V put(long key, V value) {
for (Node<V> n = table[index(key)]; n != null; n = n.next) {
if (n.key == key) {
final V o = n.value;
@@ -145,9 +169,7 @@ private final int index(final long key) {
private static class Node<V> {
final long key;
-
V value;
-
Node<V> next;
Node(final long k, final V v) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java
index 8536f1d..471a499 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java
@@ -113,6 +113,24 @@ public static int decodeUInt16(final byte[] intbuf, final int offset) {
}
/**
+ * Convert sequence of 3 bytes (network byte order) into unsigned value.
+ *
+ * @param intbuf
+ * buffer to acquire the 3 bytes of data from.
+ * @param offset
+ * position within the buffer to begin reading from. This
+ * position and the next 2 bytes after it (for a total of 3
+ * bytes) will be read.
+ * @return signed integer value that matches the 24 bits read.
+ * @since 4.9
+ */
+ public static int decodeUInt24(byte[] intbuf, int offset) {
+ int r = (intbuf[offset] & 0xff) << 8;
+ r |= intbuf[offset + 1] & 0xff;
+ return (r << 8) | (intbuf[offset + 2] & 0xff);
+ }
+
+ /**
* Convert sequence of 4 bytes (network byte order) into signed value.
*
* @param intbuf
@@ -223,6 +241,29 @@ public static void encodeInt16(final byte[] intbuf, final int offset, int v) {
}
/**
+ * Write a 24 bit integer as a sequence of 3 bytes (network byte order).
+ *
+ * @param intbuf
+ * buffer to write the 3 bytes of data into.
+ * @param offset
+ * position within the buffer to begin writing to. This position
+ * and the next 2 bytes after it (for a total of 3 bytes) will be
+ * replaced.
+ * @param v
+ * the value to write.
+ * @since 4.9
+ */
+ public static void encodeInt24(byte[] intbuf, int offset, int v) {
+ intbuf[offset + 2] = (byte) v;
+ v >>>= 8;
+
+ intbuf[offset + 1] = (byte) v;
+ v >>>= 8;
+
+ intbuf[offset] = (byte) v;
+ }
+
+ /**
* Write a 32 bit integer as a sequence of 4 bytes (network byte order).
*
* @param intbuf
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
index 86777b9..ad138bb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
@@ -618,6 +618,10 @@ public static final int prevLF(final byte[] b, int ptr, final char chrA) {
* <p>
* The last element (index <code>map.size()-1</code>) always contains
* <code>end</code>.
+ * <p>
+ * If the data contains a '\0' anywhere, the whole region is considered binary
+ * and a LineMap corresponding to a single line is returned.
+ * </p>
*
* @param buf
* buffer to scan.
@@ -629,14 +633,29 @@ public static final int prevLF(final byte[] b, int ptr, final char chrA) {
* @return a line map indexing the start position of each line.
*/
public static final IntList lineMap(final byte[] buf, int ptr, int end) {
+ int start = ptr;
+
// Experimentally derived from multiple source repositories
// the average number of bytes/line is 36. Its a rough guess
// to initially size our map close to the target.
- //
- final IntList map = new IntList((end - ptr) / 36);
- map.fillTo(1, Integer.MIN_VALUE);
- for (; ptr < end; ptr = nextLF(buf, ptr))
- map.add(ptr);
+ IntList map = new IntList((end - ptr) / 36);
+ map.add(Integer.MIN_VALUE);
+ boolean foundLF = true;
+ for (; ptr < end; ptr++) {
+ if (foundLF) {
+ map.add(ptr);
+ }
+
+ if (buf[ptr] == '\0') {
+ // binary data.
+ map = new IntList(3);
+ map.add(Integer.MIN_VALUE);
+ map.add(start);
+ break;
+ }
+
+ foundLF = (buf[ptr] == '\n');
+ }
map.add(end);
return map;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
index 1597817..ce4b7c7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
@@ -338,10 +338,11 @@ public Builder() {
* Create an empty list with at least the specified capacity.
*
* @param capacity
- * the new capacity.
+ * the new capacity; if zero or negative, behavior is the same as
+ * {@link #Builder()}.
*/
public Builder(int capacity) {
- list = new Ref[capacity];
+ list = new Ref[Math.max(capacity, 16)];
}
/** @return number of items in this builder's internal collection. */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java
index 3cb3749..a5df66e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java
@@ -114,10 +114,11 @@ public static String format(Date when) {
// up to 5 years use "year, months" rounded to months
if (ageMillis < 5 * YEAR_IN_MILLIS) {
- long years = ageMillis / YEAR_IN_MILLIS;
+ long years = round(ageMillis, MONTH_IN_MILLIS) / 12;
String yearLabel = (years > 1) ? JGitText.get().years : //
JGitText.get().year;
- long months = round(ageMillis % YEAR_IN_MILLIS, MONTH_IN_MILLIS);
+ long months = round(ageMillis - years * YEAR_IN_MILLIS,
+ MONTH_IN_MILLIS);
String monthLabel = (months > 1) ? JGitText.get().months : //
(months == 1 ? JGitText.get().month : ""); //$NON-NLS-1$
return MessageFormat.format(
@@ -140,4 +141,4 @@ private static long round(long n, long unit) {
long rounded = (n + unit / 2) / unit;
return rounded;
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java
index c95992f..727c1f4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/EolStreamTypeUtil.java
@@ -144,6 +144,11 @@ public static OutputStream wrapOutputStream(OutputStream out,
private static EolStreamType checkInStreamType(WorkingTreeOptions options,
Attributes attrs) {
+ if (attrs.isUnset("text")) {//$NON-NLS-1$
+ // "binary" or "-text" (which is included in the binary expansion)
+ return EolStreamType.DIRECT;
+ }
+
// old git system
if (attrs.isSet("crlf")) {//$NON-NLS-1$
return EolStreamType.TEXT_LF;
@@ -154,9 +159,6 @@ private static EolStreamType checkInStreamType(WorkingTreeOptions options,
}
// new git system
- if (attrs.isUnset("text")) {//$NON-NLS-1$
- return EolStreamType.DIRECT;
- }
String eol = attrs.getValue("eol"); //$NON-NLS-1$
if (eol != null)
// check-in is always normalized to LF
@@ -183,6 +185,11 @@ private static EolStreamType checkInStreamType(WorkingTreeOptions options,
private static EolStreamType checkOutStreamType(WorkingTreeOptions options,
Attributes attrs) {
+ if (attrs.isUnset("text")) {//$NON-NLS-1$
+ // "binary" or "-text" (which is included in the binary expansion)
+ return EolStreamType.DIRECT;
+ }
+
// old git system
if (attrs.isSet("crlf")) {//$NON-NLS-1$
return FORCE_EOL_LF_ON_CHECKOUT ? EolStreamType.TEXT_LF
@@ -194,9 +201,6 @@ private static EolStreamType checkOutStreamType(WorkingTreeOptions options,
}
// new git system
- if (attrs.isUnset("text")) {//$NON-NLS-1$
- return EolStreamType.DIRECT;
- }
String eol = attrs.getValue("eol"); //$NON-NLS-1$
if (eol != null && "crlf".equals(eol)) //$NON-NLS-1$
return EolStreamType.TEXT_CRLF;
diff --git a/pom.xml b/pom.xml
index b74de4e..9a8050a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,7 +51,7 @@
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
<packaging>pom</packaging>
- <version>4.8.1-SNAPSHOT</version>
+ <version>4.9.9-SNAPSHOT</version>
<name>JGit - Parent</name>
<url>${jgit-url}</url>
@@ -200,7 +200,7 @@
<javaewah-version>1.1.6</javaewah-version>
<junit-version>4.12</junit-version>
<test-fork-count>1C</test-fork-count>
- <args4j-version>2.0.15</args4j-version>
+ <args4j-version>2.33</args4j-version>
<commons-compress-version>1.6</commons-compress-version>
<osgi-core-version>4.3.1</osgi-core-version>
<servlet-api-version>3.1.0</servlet-api-version>
@@ -212,7 +212,7 @@
<maven-javadoc-plugin-version>2.10.4</maven-javadoc-plugin-version>
<tycho-extras-version>1.0.0</tycho-extras-version>
<gson-version>2.2.4</gson-version>
- <findbugs-maven-plugin-version>3.0.4</findbugs-maven-plugin-version>
+ <spotbugs-maven-plugin-version>3.0.6</spotbugs-maven-plugin-version>
<maven-surefire-report-plugin-version>2.20</maven-surefire-report-plugin-version>
<!-- Properties to enable jacoco code coverage analysis -->
@@ -264,7 +264,7 @@
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
- <version>3.6.1</version>
+ <version>3.6.2</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.8</source>
@@ -302,19 +302,19 @@
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-javac</artifactId>
- <version>2.8.1</version>
+ <version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-javac-errorprone</artifactId>
- <version>2.8.1</version>
+ <version>2.8.2</version>
</dependency>
<!-- override plexus-compiler-javac-errorprone's dependency on
Error Prone with the latest version -->
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
- <version>2.0.19</version>
+ <version>2.1.1</version>
</dependency>
</dependencies>
</plugin>
@@ -371,9 +371,9 @@
</plugin>
<plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>findbugs-maven-plugin</artifactId>
- <version>${findbugs-maven-plugin-version}</version>
+ <groupId>com.github.hazendaz.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
+ <version>${spotbugs-maven-plugin-version}</version>
<configuration>
<findbugsXmlOutput>true</findbugsXmlOutput>
<failOnError>false</failOnError>
@@ -579,9 +579,9 @@
<version>2.5</version>
</plugin>
<plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>findbugs-maven-plugin</artifactId>
- <version>${findbugs-maven-plugin-version}</version>
+ <groupId>com.github.hazendaz.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
+ <version>${spotbugs-maven-plugin-version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -751,8 +751,8 @@
<build>
<plugins>
<plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>findbugs-maven-plugin</artifactId>
+ <groupId>com.github.hazendaz.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
diff --git a/tools/bazlets.bzl b/tools/bazlets.bzl
index f97b72c..f089af4 100644
--- a/tools/bazlets.bzl
+++ b/tools/bazlets.bzl
@@ -1,10 +1,12 @@
+load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
+
NAME = "com_googlesource_gerrit_bazlets"
def load_bazlets(
commit,
local_path = None):
if not local_path:
- native.git_repository(
+ git_repository(
name = NAME,
remote = "https://gerrit.googlesource.com/bazlets",
commit = commit,