diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
index 72084f84..590c9593 100644
--- a/.github/CODE_OF_CONDUCT.md
+++ b/.github/CODE_OF_CONDUCT.md
@@ -2,75 +2,131 @@
## Our Pledge
-In the interest of fostering an open and welcoming environment, we as
-contributors and maintainers pledge to making participation in our project and
-our community a harassment-free experience for everyone, regardless of age, body
-size, disability, ethnicity, sex characteristics, gender identity and expression,
-level of experience, education, socio-economic status, nationality, personal
-appearance, race, religion, or sexual identity and orientation.
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
## Our Standards
-Examples of behavior that contributes to creating a positive environment
-include:
+Examples of behavior that contributes to a positive environment for our
+community include:
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+ overall community
-Examples of unacceptable behavior by participants include:
+Examples of unacceptable behavior include:
-* The use of sexualized language or imagery and unwelcome sexual attention or
- advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
+* The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
- address, without explicit permission
+* Publishing others' private information, such as a physical or email
+ address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- professional setting
+ professional setting
-## Our Responsibilities
+## Enforcement Responsibilities
-Project maintainers are responsible for clarifying the standards of acceptable
-behavior and are expected to take appropriate and fair corrective action in
-response to any instances of unacceptable behavior.
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
-Project maintainers have the right and responsibility to remove, edit, or
-reject comments, commits, code, wiki edits, issues, and other contributions
-that are not aligned to this Code of Conduct, or to ban temporarily or
-permanently any contributor for other behaviors that they deem inappropriate,
-threatening, offensive, or harmful.
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
## Scope
-This Code of Conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community. Examples of
-representing a project or community include using an official project e-mail
-address, posting via an official social media account, or acting as an appointed
-representative at an online or offline event. Representation of a project may be
-further defined and clarified by project maintainers.
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported by contacting the project team at graham@alt-three.com. All
-complaints will be reviewed and investigated and will result in a response that
-is deemed necessary and appropriate to the circumstances. The project team is
-obligated to maintain confidentiality with regard to the reporter of an incident.
-Further details of specific enforcement policies may be posted separately.
+reported to the community leaders responsible for enforcement at
+hello@gjcampbell.co.uk.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
-Project maintainers who do not follow or enforce the Code of Conduct in good
-faith may face temporary or permanent repercussions as determined by other
-members of the project's leadership.
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
## Attribution
-This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
-available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.1, available at
+[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
-[homepage]: https://www.contributor-covenant.org
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
-For answers to common questions about this code of conduct, see
-https://www.contributor-covenant.org/faq
+For answers to common questions about this code of conduct, see the FAQ at
+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
+at [https://www.contributor-covenant.org/translations][translations].
+
+[homepage]: https://www.contributor-covenant.org
+[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+[Mozilla CoC]: https://github.com/mozilla/diversity
+[FAQ]: https://www.contributor-covenant.org/faq
+[translations]: https://www.contributor-covenant.org/translations
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 0f98282a..d33db7e8 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -2,16 +2,16 @@
Contributions are **welcome** and will be fully **credited**.
-We accept contributions via pull requests on Github. Please review these guidelines before continuing.
+We accept contributions via pull requests on GitHub. Please review these guidelines before continuing.
## Guidelines
-* Please follow the [PSR-2 Coding Style Guide](https://www.php-fig.org/psr/psr-2/), enforced by [StyleCI](https://styleci.io/).
+* Please follow the [PSR-12 Coding Style Guide](https://www.php-fig.org/psr/psr-12/), enforced by [StyleCI](https://styleci.io/).
* Ensure that the current tests pass, and if you've added something new, add the tests where relevant.
-* Send a coherent commit history, making sure each individual commit in your pull request is meaningful.
+* Send a coherent commit history, making sure each commit in your pull request is meaningful.
* You may need to [rebase](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) to avoid merge conflicts.
-* If you are changing or adding to the behaviour or public api, you may need to update the docs.
-* Please remember that we follow [SemVer](https://semver.org/).
+* If you are changing or adding to the behaviour or public API, you may need to update the docs.
+* Please remember that we follow [Semantic Versioning](https://semver.org/).
## Running Tests
@@ -27,5 +27,6 @@ Then run [PHPUnit](https://phpunit.de/):
$ vendor/bin/phpunit
```
-* The tests will be automatically run by [Travis CI](https://travis-ci.org/) against pull requests.
-* We also have [StyleCI](https://styleci.io/) setup to automatically fix any code style issues.
+* A script `test-git-version.sh` is available in repository to test gitlib against many git versions.
+* The tests will be automatically run by [GitHub Actions](https://github.com/features/actions) against pull requests.
+* We also have [StyleCI](https://styleci.io/) set up to automatically fix any code style issues.
diff --git a/.github/SECURITY.md b/.github/SECURITY.md
index 79a128e7..0c3e8a24 100644
--- a/.github/SECURITY.md
+++ b/.github/SECURITY.md
@@ -2,20 +2,13 @@
## Supported Versions
-After each new major (minor) release, the previous release will be supported
-for no less than 12 (6) months, unless explictly otherwise. This may mean that
+After each new major release, the previous release will be supported for no
+less than 12 months, unless explictly stated otherwise. This may mean that
there are multiple supported versions at any given time.
## Reporting a Vulnerability
If you discover a security vulnerability within this package, please send an
-email to one the security contacts. All security vulnerabilities will be
-promptly addressed. Please do not disclose security-related issues publicly
-until a fix has been announced.
-
-### Security Contacts
-
-* Graham Campbell (graham@alt-three.com)
-* Julien Didier (genzo.wm@gmail.com)
-* Grégoire Pineau (lyrixx@lyrixx.info)
-* Alexandre Salomé (alexandre.salome@gmail.com)
+email to security@tidelift.com. All security vulnerabilities will be promptly
+addressed. Please do not disclose security-related issues publicly until a fix
+has been announced.
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 00000000..4e611ce8
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,40 @@
+name: Tests
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+
+jobs:
+ tests:
+ name: Test PHP ${{ matrix.php }} ${{ matrix.name }}
+ runs-on: ubuntu-24.04
+ strategy:
+ fail-fast: false
+ matrix:
+ php: ['8.1', '8.2', '8.3', '8.4']
+ composer-flags: ['']
+ name: ['']
+ include:
+ - php: '8.0'
+ composer-flags: '--prefer-lowest'
+ name: '(prefer lowest dependencies)'
+
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+
+ - name: Setup Problem Matchers
+ run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
+
+ - name: Install Composer dependencies
+ run: |
+ composer update --prefer-dist --no-interaction ${{ matrix.composer-flags }}
+
+ - name: Execute PHPUnit
+ run: vendor/bin/phpunit
diff --git a/.gitignore b/.gitignore
index abb9c75a..376a2cd1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
-/vendor
+/.phpunit.result.cache
/composer.lock
/phpunit.xml
+/vendor
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 39cdc5c2..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-language: php
-
-dist: xenial
-
-php:
- - 5.6
- - 7.0
- - 7.1
- - 7.2
- - 7.3
-
-install: travis_retry composer install --no-interaction --no-progress --no-suggest
-
-script: vendor/bin/phpunit
diff --git a/README.md b/README.md
index 58fa583d..e0dcade7 100644
--- a/README.md
+++ b/README.md
@@ -1,26 +1,73 @@
-Git lib for Gitonomy
-====================
+Gitlib for Gitonomy
+===================
-[](https://travis-ci.org/gitonomy/gitlib)
-[](https://github.styleci.io/repos/5709354)
-
+[](https://github.com/gitonomy/gitlib/actions?query=workflow%3ATests+branch%3A1.3)
+[](https://github.styleci.io/repos/5709354?branch=1.3)
+[](https://opensource.org/licenses/MIT)
+[](https://packagist.org/packages/gitonomy/gitlib)
-This library provides methods to access Git repository from PHP.
+This library provides methods to access Git repository from PHP 5.6+.
It makes shell calls, which makes it less performant than any solution.
Anyway, it's convenient and don't need to build anything to use it. That's how we love it.
-*Documentation*: http://gitonomy.com/doc/gitlib/master/
+Quick Start
+-----------
----
+You can install gitlib using [Composer](https://getcomposer.org/). Simply require the version you need:
-
+```bash
+$ composer require gitonomy/gitlib
+```
+
+or edit your `composer.json` file by hand:
+
+```json
+{
+ "require": {
+ "gitonomy/gitlib": "^1.3"
+ }
+}
+```
+
+Example Usage
+-------------
+
+```php
+getReferences()->getBranches() as $branch) {
+ echo '- '.$branch->getName().PHP_EOL;
+}
+
+$repository->run('fetch', ['--all']);
+```
+
+API Documentation
+-----------------
+
++ [Admin](doc/admin.md)
++ [Blame](doc/blame.md)
++ [Blob](doc/blob.md)
++ [Branch](doc/branch.md)
++ [Commit](doc/commit.md)
++ [Diff](doc/diff.md)
++ [Hooks](doc/hooks.md)
++ [Log](doc/log.md)
++ [References](doc/references.md)
++ [Repository](doc/repository.md)
++ [Revision](doc/revision.md)
++ [Tree](doc/tree.md)
++ [Working Copy](doc/workingcopy.md)
+
+For Enterprise
+--------------
+
+Available as part of the Tidelift Subscription
+
+The maintainers of `gitonomy/gitlib` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-gitonomy-gitlib?utm_source=packagist-gitonomy-gitlib&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
diff --git a/composer.json b/composer.json
index 32165d10..a3de0536 100644
--- a/composer.json
+++ b/composer.json
@@ -5,22 +5,25 @@
"authors": [
{
"name": "Graham Campbell",
- "email": "graham@alt-three.com"
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Julien Didier",
- "email": "genzo.wm@gmail.com"
+ "email": "genzo.wm@gmail.com",
+ "homepage": "https://github.com/juliendidier"
},
{
"name": "Grégoire Pineau",
- "email": "lyrixx@lyrixx.info"
+ "email": "lyrixx@lyrixx.info",
+ "homepage": "https://github.com/lyrixx"
},
{
"name": "Alexandre Salomé",
- "email": "alexandre.salome@gmail.com"
+ "email": "alexandre.salome@gmail.com",
+ "homepage": "https://github.com/alexandresalome"
}
],
- "homepage": "http://gitonomy.com/",
"autoload": {
"psr-4": {
"Gitonomy\\Git\\": "src/Gitonomy/Git/"
@@ -32,19 +35,21 @@
}
},
"require": {
- "php": "^5.6 || ^7.0",
- "symfony/process": "^3.4|^4.0"
+ "php": "^8.0",
+ "ext-pcre": "*",
+ "symfony/polyfill-mbstring": "^1.7",
+ "symfony/process": "^5.4 || ^6.0 || ^7.0"
},
"require-dev": {
- "phpunit/phpunit": "^5.7|^6.5",
+ "ext-fileinfo": "*",
+ "phpspec/prophecy-phpunit": "^2.0",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.20 || ^9.5.9",
"psr/log": "^1.0"
},
- "suggest": {
- "psr/log": "Required to use loggers for reporting of execution"
+ "config": {
+ "preferred-install": "dist",
+ "sort-packages": true
},
- "extra": {
- "branch-alias": {
- "dev-master": "1.1-dev"
- }
- }
+ "minimum-stability": "dev",
+ "prefer-stable": true
}
diff --git a/doc/admin.md b/doc/admin.md
new file mode 100644
index 00000000..d4776804
--- /dev/null
+++ b/doc/admin.md
@@ -0,0 +1,77 @@
+Create and access git repositories
+==================================
+
+gitlib provides methods to initialize new repositories.
+
+Create a repository
+-------------------
+
+To initialize a new repository, use method `Admin::init`.
+
+```php
+// Initialize a bare repository
+$repository = Gitonomy\Git\Admin::init('/path/to/repository');
+
+// Initialize a non-bare repository
+$repository = Gitonomy\Git\Admin::init('/path/to/repository', false);
+```
+
+Default behavior is to create a bare repository. If you want to
+initialize a repository with a working copy,pass `false` as third
+argument of Repository constructor.
+
+Cloning repositories
+--------------------
+
+You can clone a repository from an URL by doing:
+
+```php
+// Clone to a bare repository
+$repository = Gitonomy\Git\Admin::cloneTo('/tmp/gitlib', 'https://github.com/gitonomy/gitlib.git');
+
+// Clone to a non-bare repository
+$repository = Gitonomy\Git\Admin::cloneTo('/tmp/gitlib', 'https://github.com/gitonomy/gitlib.git', false);
+```
+
+Default behavior is to clone in a bare repository.
+
+You can also clone a repository and point it to a specific branch. In a
+non-bare repository, this branch will be checked out:
+
+```php
+// Clone to a bare repository
+$repository = Gitonomy\Git\Admin::cloneBranchTo('/tmp/gitlib', 'https://github.com/gitonomy/gitlib.git', 'a-branch');
+
+// Clone to a non-bare repository
+$repository = Gitonomy\Git\Admin::cloneBranchTo('/tmp/gitlib', 'https://github.com/gitonomy/gitlib.git', 'a-branch', false);
+```
+
+Clone a Repository object
+-------------------------
+
+If you already have a Repository instance and want to clone it, you can
+use this shortcut:
+
+```php
+$new = $repository->cloneTo('/tmp/clone');
+```
+
+Mirror a repository
+-------------------
+
+If you want to mirror fully a repository and all references, use the
+`mirrorTo` method. This method takes only two arguments, where to mirror
+and what to mirror:
+
+```php
+// Mirror to a bare repository
+$mirror = Gitonomy\Git\Admin::mirrorTo('/tmp/mirror', 'https://github.com/gitonomy/gitlib.git');
+
+// Mirror to a non-bare repository
+$mirror = Gitonomy\Git\Admin::mirrorTo('/tmp/mirror', 'https://github.com/gitonomy/gitlib.git', false);
+```
+
+### References
+
+-
+-
diff --git a/doc/api/admin.rst b/doc/api/admin.rst
deleted file mode 100644
index f1ec8b66..00000000
--- a/doc/api/admin.rst
+++ /dev/null
@@ -1,76 +0,0 @@
-Create and access git repositories
-==================================
-
-gitlib provides methods to initialize new repositories.
-
-Create a repository
--------------------
-
-To initialize a new repository, use method ``Admin::init``.
-
-.. code-block:: php
-
- // Initialize a bare repository
- $repository = Gitonomy\Git\Admin::init('/path/to/repository');
-
- // Initialize a non-bare repository
- $repository = Gitonomy\Git\Admin::init('/path/to/repository', false);
-
-Default behavior is to create a bare repository. If you want to initialize a
-repository with a working copy,pass ``false`` as third argument of Repository
-constructor.
-
-Cloning repositories
---------------------
-
-You can clone a repository from an URL by doing:
-
-.. code-block:: php
-
- // Clone to a bare repository
- $repository = Gitonomy\Git\Admin::cloneTo('/tmp/gitlib', 'https://github.com/gitonomy/gitlib.git');
-
- // Clone to a non-bare repository
- $repository = Gitonomy\Git\Admin::cloneTo('/tmp/gitlib', 'https://github.com/gitonomy/gitlib.git', false);
-
-Default behavior is to clone in a bare repository.
-
-You can also clone a repository and point it to a specific branch. In a non-bare repository, this branch will be checked out:
-
-.. code-block:: php
-
- // Clone to a bare repository
- $repository = Gitonomy\Git\Admin::cloneBranchTo('/tmp/gitlib', 'https://github.com/gitonomy/gitlib.git', 'a-branch');
-
- // Clone to a non-bare repository
- $repository = Gitonomy\Git\Admin::cloneBranchTo('/tmp/gitlib', 'https://github.com/gitonomy/gitlib.git', 'a-branch' false);
-
-Clone a Repository object
--------------------------
-
-If you already have a Repository instance and want to clone it, you can use this shortcut:
-
-.. code-block:: php
-
- $new = $repository->cloneTo('/tmp/clone');
-
-Mirror a repository
--------------------
-
-If you want to mirror fully a repository and all references, use the ``mirrorTo`` method. This method
-takes only two arguments, where to mirror and what to mirror:
-
-.. code-block:: php
-
- // Mirror to a bare repository
- $mirror = Gitonomy\Git\Admin::mirrorTo('/tmp/mirror', 'https://github.com/gitonomy/gitlib.git');
-
- // Mirror to a non-bare repository
- $mirror = Gitonomy\Git\Admin::mirrorTo('/tmp/mirror', 'https://github.com/gitonomy/gitlib.git', false);
-
-
-References
-::::::::::
-
-* http://linux.die.net/man/1/git-init
-* http://linux.die.net/man/1/git-clone
diff --git a/doc/api/blame.rst b/doc/api/blame.rst
deleted file mode 100644
index 7fcbdcdc..00000000
--- a/doc/api/blame.rst
+++ /dev/null
@@ -1,54 +0,0 @@
-Blaming files
-=============
-
-Line-per-line iteration
------------------------
-
-To iterate on lines of a blame:
-
-.. code-block:: php
-
- $blame = $repository->getBlame('master', 'README.md');
-
- foreach ($blame->getLines() as $lineNumber => $line) {
- $commit = $line->getCommit();
- echo $lineNumber.': '.$line->getContent()." - ".$commit->getAuthorName()."\n";
- }
-
-The *getLines* method returns an array indexed starting from 1.
-
-As you can see, you can access the commit object related to the line you are iterating on.
-
-If you want to access directly a line:
-
-.. code-block:: php
-
- $line = $blame->getLine(32);
-
-The Line object
----------------
-
-LineObject represents an item of the blame file. It is composed of those informations:
-
-.. code-block:: php
-
- $line->getCommit(); // returns a Commit
- $line->getContent(); // returns text
-
- // you can access author from commmit:
- $author = $line->getCommit()->getAuthorName();
-
-Group reading by commit
------------------------
-
-If you plan to display it, you'll probably need a version where lines from same commit are grouped.
-
-To do so, use the *getGroupedLines* method that will return an array like this:
-
-.. code-block:: php
-
- $blame = array(
- array(Commit, array(1 => Line, 2 => Line, 3 => Line)),
- array(Commit, array(4 => Line)),
- array(Commit, array(5 => Line, 6 => Line))
- )
diff --git a/doc/api/branch.rst b/doc/api/branch.rst
deleted file mode 100644
index c7c0c408..00000000
--- a/doc/api/branch.rst
+++ /dev/null
@@ -1,16 +0,0 @@
-Branch
-======
-
-To access a *Branch*, starting from a repository object:
-
-.. code-block:: php
-
- $repository = new Gitonomy\Git\Repository('/path/to/repository');
- $branch = $repository->getReferences()->getBranch('master');
-
-You can check is the branch is a local or remote one:
-
-.. code-block:: php
-
- $branch->isLocal();
- $branch->isRemote();
diff --git a/doc/api/commit.rst b/doc/api/commit.rst
deleted file mode 100644
index d11ea810..00000000
--- a/doc/api/commit.rst
+++ /dev/null
@@ -1,168 +0,0 @@
-Commit
-======
-
-To access a *Commit*, starting from a repository object:
-
-.. code-block:: php
-
- $repository = new Gitonomy\Git\Repository('/path/to/repository');
- $commit = $repository->getCommit('a7c8d2b4');
-
-Browsing parents
-----------------
-
-A *Commit* can have a natural number of parents:
-
-* **no parent**: it's an initial commit, the root of a tree
-* **one parent**: it means it's not a merge, just a regular commit
-* **many parents**: it's a merge-commit
-
-You have 2 methods available for accessing parents:
-
-.. code-block:: php
-
- // Access parent hashes
- $hashes = $commit->getParentHashes();
-
- // Access parent commit objects
- $commits = $commit->getParents();
-
-For example, if you want to display all parents, starting from a commit:
-
-.. code-block:: php
-
- function displayLog(Gitonomy\Git\Commit $commit) {
- echo '- '.$commit->getShortMessage()."\n";
- foreach ($commit->getParents() as $parent) {
- displayLog($parent);
- }
- }
-
-Notice that this function will first display all commits from first merged
-branch and then display all commits from next branch, and so on.
-
-Accessing tree
---------------
-
-The tree object contains the reference to the files associated to a given
-commit. Every commit has one and only one tree, referencing all files and
-folders of a given state for a project. For more informations about the tree,
-see the chapter dedicated to it.
-
-To access a tree starting from a commit:
-
-.. code-block:: php
-
- // Returns the tree hash
- $tree = $commit->getTreeHash();
-
- // Returns the tree object
- $tree = $commit->getTree();
-
-Author & Committer informations
--------------------------------
-
-Each commit has two authoring informations: an author and a committer. The
-author is the creator of the modification, authoring a modification in the
-repository. The committer is responsible of introducing this modification to
-the repository.
-
-You can access informations from author and committer using those methods:
-
-.. code-block:: php
-
- // Author
- $commit->getAuthorName();
- $commit->getAuthorEmail();
- $commit->getAuthorDate(); // returns a DateTime object
-
- // Committer
- $commit->getCommitterName();
- $commit->getCommitterEmail();
- $commit->getCommitterDate(); // returns a DateTime object
-
-Commit message and short message
---------------------------------
-
-Each commit also has a message, associated to the modification. This message
-can be multilined.
-
-To access the message, you can use the *getMessage* method:
-
-.. code-block:: php
-
- $commit->getMessage();
-
-For your convenience, this library provides a shortcut method to keep only the
-first line or first 50 characters if the first line is too long:
-
-.. code-block:: php
-
- $commit->getShortMessage();
-
-You can customize it like this:
-
-.. code-block:: php
-
- $commit->getShortMessage(45, true, '.');
-
-* The first parameter is the max length of the message.
-* The second parameter determine if the last word should be cut or preserved
-* The third parameter is the separator
-
-There are also two other methods for your convenience:
-
-.. code-block:: php
-
- // The first line
- $commit->getSubjectMessage();
-
- // The body (rest of the message)
- $commit->getBodyMessage();
-
-Diff of a commit
-----------------
-
-You can check the modifications introduced by a commit using the *getDiff*
-method. When you request a diff for a commit, depending of the number of
-parents, the strategy will be different:
-
-* If you have *no parent*, the diff will be the content of the tree
-* If you only have *one parent*, the diff will be between the commit and his
- parent
-* If you have *multiple parents*, the diff will be the difference between the
- commit and the first common ancestor of all parents
-
-For more informations about the diff API, read the related chapter.
-
-To access the *Diff* object of a commit, use the method *getDiff*:
-
-.. code-block:: php
-
- $diff = $commit->getDiff();
-
-Last modification of a file
----------------------------
-
-To know the last modification of a file, you can use the *getLastModification*
-method on a commit.
-
-Here is a very straightforward example:
-
-.. code-block:: php
-
- $last = $commit->getLastModification('README');
-
- echo "Last README modification:\n";
- echo" Author: ".$last->getAuthorName()."\n";
- echo" Date: ".$last->getAuthorDate()->format('d/m/Y')."\n";
- echo" Message: ".$last->getMessage();
-
-Find every branches containing a commit
----------------------------------------
-
-.. code-block:: php
-
- $branches = $commit->getIncludingBranches($includeLocalBranches, $includeRemoteBranches);
- $localBranches = $commit->getIncludingBranches(true, false);
- $remoteBranches = $commit->getIncludingBranches(false, true);
diff --git a/doc/api/diff.rst b/doc/api/diff.rst
deleted file mode 100644
index badd0776..00000000
--- a/doc/api/diff.rst
+++ /dev/null
@@ -1,102 +0,0 @@
-Computing diff
-==============
-
-Even if git is a diff-less storage engine, it's possible to compute them.
-
-To compute a diff in git, you need to specify a *revision*. This revision can
-be a commit (*2bc7a8*) or a range (*2bc7a8..ff4c21b*).
-
-For more informations about git revisions: *man gitrevisions*.
-
-When you have decided the revision you want and have your *Repository* object,
-you can call the *getDiff* method on the repository:
-
-.. code-block:: php
-
- $diff = $repository->getDiff('master@{2 days ago}..master');
-
-You can also access it from a *Log* object:
-
-.. code-block:: php
-
- $log = $repository->getLog('master@{2 days ago}..master');
- $diff = $log->getDiff();
-
-Iterating a diff
-----------------
-
-When you have a *Diff* object, you can iterate over files using method
-*getFiles()*. This method returns a list of *File* objects, who represents the
-modifications for a single file.
-
-.. code-block:: php
-
- $files = $diff->getFiles();
- echo sprintf("%s files modified", count($files));
-
- foreach ($files as $fileDiff) {
- echo sprintf("Old name: (%s) %s\n", $fileDiff->getOldMode(), $fileDiff->getOldName());
- echo sprintf("New name: (%s) %s\n", $fileDiff->getNewMode(), $fileDiff->getNewName());
- }
-
-The File object
----------------
-
-Here is an exhaustive list of the *File* class methods:
-
-.. code-block:: php
-
- $file->getOldName();
- $file->getNewName();
- $file->getOldDiff();
- $file->getNewDiff();
-
- $file->isCreation();
- $file->isDeletion();
- $file->isModification();
-
- $file->isRename();
- $file->isChangeMode();
-
- $file->getAdditions(); // Number of added lines
- $file->getDeletions(); // Number of deleted lines
-
- $file->isBinary(); // Binary files have no "lines"
-
- $file->getChanges(); // See next chapter
-
-The FileChange object
----------------------
-
-.. note::
-
- This part of API is not very clean, very consistent. If you have any idea
- or suggestion on how to enhance this, your comment would be appreciated.
-
-A *File* object is composed of many changes. For each of those changes,
-a *FileChange* object is associated.
-
-To access changes from a file, use the *getChanges* method:
-
-.. code-block:: php
-
- $changes = $file->getChanges();
- foreach ($changes as $change) {
- foreach ($lines as $data) {
- list ($type, $line) = $data;
- if ($type === FileChange::LINE_CONTEXT) {
- echo ' '.$line."\n";
- } elseif ($type === FileChange::LINE_ADD) {
- echo '+'.$line."\n";
- } else {
- echo '-'.$line."\n";
- }
- }
- }
-
-To get line numbers, use the range methods:
-
-.. code-block:: php
-
- echo sprintf("Previously from line %s to %s\n", $change->getOldRangeStart(), $change->getOldRangeEnd());
- echo sprintf("Now from line %s to %s\n", $change->getNewRangeStart(), $change->getNewRangeEnd());
diff --git a/doc/api/hooks.rst b/doc/api/hooks.rst
deleted file mode 100644
index 20c4abec..00000000
--- a/doc/api/hooks.rst
+++ /dev/null
@@ -1,76 +0,0 @@
-Hooks
-=====
-
-It's possible to define custom hooks on any repository with git. Those hooks
-are located in the *.git/hooks* folder.
-
-Those files need to be executable. For convenience, gitlib will set them to
-*777*.
-
-With *gitlib*, you can manage hooks over a repository using the *Hooks* object.
-
-To access it from a repository, use the *getHooks* method on a *Repository*
-object:
-
-.. code-block:: php
-
- $hooks = $repository->getHooks();
-
-Reading hooks
--------------
-
-To read the content of a hook, use the *get* method like this:
-
-.. code-block:: php
-
- $content = $hooks->get('pre-receive'); // returns a string
-
-If the hook does not exist, an exception will be thrown (*InvalidArgumentException*).
-
-You can test if a hook is present using the method *has*:
-
-.. code-block:: php
-
- $hooks->has('pre-receive'); // a boolean indicating presence
-
-Inserting hooks
----------------
-
-You can modify a hook in two different ways: creating a new file or using a symlink.
-
-To create the hook using a symlink:
-
-.. code-block:: php
-
- $hooks->setSymlink('pre-receive', '/path/to/file-to-link');
-
-If the hook already exist, a *LogicException* will be thrown. If an error occured
-during symlink creation, a *RuntimeException* will be thrown.
-
-If you want to directly create a new file in hooks directory, use the
-method *set*. This method will create a new file, put content in it and make it
-executable:
-
-.. code-block:: php
-
- $content = <<set('pre-receive', $content);
-
-If the hook already exists, a *LogicException* will be thrown.
-
-Removing hooks
---------------
-
-To remove a hook from a repository, use the function *remove*:
-
-.. code-block:: php
-
- $hooks->remove('pre-receive');
diff --git a/doc/api/log.rst b/doc/api/log.rst
deleted file mode 100644
index 5a795b9e..00000000
--- a/doc/api/log.rst
+++ /dev/null
@@ -1,54 +0,0 @@
-Getting log history
-===================
-
-Crawling manually commits and parents to browse history is surely a good
-solution. But when it comes to ordering them or aggregate them from multiple
-branches, we tend to use ``git log``.
-
-To get a *Log* object from a repository:
-
-.. code-block:: php
-
- $log = $repository->getLog();
-
-You can pass four arguments to *getLog* method:
-
-.. code-block:: php
-
- // Global log for repository
- $log = $repository->getLog();
-
- // Log for master branch
- $log = $repository->getLog('master');
-
- // Returns last 10 commits on README file
- $log = $repository->getLog('master', 'README', 0, 10);
-
- // Returns last 10 commits on README or UPGRADE files
- $log = $repository->getLog('master', array('README', 'UPGRADE'), 0, 10);
-
-Counting
---------
-
-If you want to count overall commits, without offset or limit, use the *countCommits* method:
-
-.. code-block:: php
-
- echo sprintf("This log contains %s commits\n", $log->countCommits());
-
- // Countable interface
- echo sprintf("This log contains %s commits\n", count($log));
-
-Offset and limit
-----------------
-
-Use those methods:
-
-.. code-block:: php
-
- $log->setOffset(32);
- $log->setLimit(40);
-
- // or read it:
- $log->getOffset();
- $log->getLimit();
diff --git a/doc/api/references.rst b/doc/api/references.rst
deleted file mode 100644
index 5b7fa975..00000000
--- a/doc/api/references.rst
+++ /dev/null
@@ -1,91 +0,0 @@
-Tags and branches
-=================
-
-Accessing tags and branches
----------------------------
-
-With *gitlib*, you can access them via the *ReferenceBag* object. To get this
-object from a *Repository*, use the *getReferences* method:
-
-.. code-block:: php
-
- $references = $repository->getReferences();
-
-First, you can test existence of tags and branches like this:
-
-.. code-block:: php
-
- if ($references->hasBranch('master') && $references->hasTag('0.1')) {
- echo "Good start!";
- }
-
-If you want to access all branches or all tags:
-
-.. code-block:: php
-
- $branches = $references->getBranches();
- $localBranches = $references->getLocalBranches();
- $remoteBranches = $references->getRemoteBranches();
- $tags = $references->getTags();
- $all = $references->getAll();
-
-To get a given branch or tag, call *getBranch* or *getTag* on the
-*ReferenceBag*. Those methods return *Branch* and *Tag* objects:
-
-.. code-block:: php
-
- $master = $references->getBranch('master');
- $feat123 = $references->getLocalBranch('feat123');
- $feat456 = $references->getRemoteBranch('origin/feat456');
- $v0_1 = $references->getTag('0.1');
-
-If the reference cannot be resolved, a *ReferenceNotFoundException* will be
-thrown.
-
-On each of those objects, you can access those informations:
-
-.. code-block:: php
-
- // Get the associated commit
- $commit = $master->getCommit();
-
- // Get the commit hash
- $hash = $master->getCommitHash();
-
- // Get the last modification
- $lastModification = $master->getLastModification();
-
-Create and delete reference
----------------------------
-
-You can create new tags and branches on repository, using helper methods
-on ReferenceBag object:
-
-.. code-block:: php
-
- // create a branch
- $references = $repository->getReferences();
- $branch = $references->createBranch('foobar', 'a8b7e4...'); // commit to reference
-
- // create a tag
- $references = $repository->getReferences();
- $tag = $references->createTag('0.3', 'a8b7e4...'); // commit to reference
-
- // delete a branch or a tag
- $branch->delete();
-
-Resolution from a commit
-------------------------
-
-To resolve a branch or a commit from a commit, you can use the *resolveTags*
-and *resolveBranches* methods on it:
-
-.. code-block:: php
-
- $branches = $references->resolveBranches($commit);
- $tags = $references->resolveTags($commit);
-
- // Resolve branches and tags
- $all = $references->resolve($commit);
-
-You can pass a *Commit* object or a hash to the method, gitlib will handle it.
diff --git a/doc/api/repository.rst b/doc/api/repository.rst
deleted file mode 100644
index 53fa07a3..00000000
--- a/doc/api/repository.rst
+++ /dev/null
@@ -1,135 +0,0 @@
-Repository methods
-==================
-
-Creating a *Repository* object is possible, providing a *path* argument to the
-constructor:
-
-.. code-block:: php
-
- $repository = new Repository('/path/to/repo');
-
-Repository options
-------------------
-
-The constructor of Repository takes an additional parameter: ``$options``.
-This parameter can be used used to tune behavior of library.
-
-Available options are:
-
-* **debug** (default: true): Enables exception when edge cases are met
-* **environment_variables**: (default: none) An array of environment variables to be set in sub-process
-* **logger**: (default: none) Logger to use for reporting of execution (a ``Psr\Log\LoggerInterface``)
-* **command**: (default: ``git``) Specify command to execute to run git
-* **working_dir**: If you are using multiple working directories, this option is for you
-
-An example:
-
-.. code-block:: php
-
- $repository = new Repository('/path/to/repo', array(
- 'debug' => true,
- 'logger' => new Monolog\Logger()
- ));
-
-Test if a repository is bare
-----------------------------
-
-On a *Repository* object, you can call method *isBare* to test if your repository is bare or not:
-
-.. code-block:: php
-
- $repository->isBare();
-
-Compute size of a repository
-----------------------------
-
-To know how much size a repository is using on your drive, you can use ``getSize`` method on a *Repository* object.
-
-.. warning:: This command was only tested with linux.
-
-The returned size is in kilobytes:
-
-.. code-block:: php
-
- $size = $repository->getSize();
-
- echo "Your repository size is ".$size."KB";
-
-Access HEAD
------------
-
-``HEAD`` represents in git the version you are working on (in working tree).
-Your ``HEAD`` can be attached (using a reference) or detached (using a commit).
-
-.. code-block:: php
-
- $head = $repository->getHead(); // Commit or Reference
- $head = $repository->getHeadCommit(); // Commit
-
- if ($repository->isHeadDetached()) {
- echo "Sorry man\n";
- }
-
-Options for repository
-----------------------
-
-Logger
-......
-
-If you are developing, you may appreciate to have a logger inside repository, telling you every executed command.
-
-You call method ``setLogger`` as an option on repository creation:
-
-.. code-block:: php
-
- $repository->setLogger(new Monolog\Logger('repository'));
-
- $repository->run('fetch', array('--all'));
-
-You can also specify as an option on repository creation:
-
- $logger = new Monolog\Logger('repository');
- $repository = new Repository('/path/foo', array('logger' => $logger));
-
- $repository->run('fetch', array('--all'));
-
-This will output:
-
-.. code-block:: text
-
- info run command: fetch "--all"
- debug last command (fetch) duration: 23.24ms
- debug last command (fetch) return code: 0
- debug last command (fetch) output: Fetching origin
-
-Disable debug-mode
-..................
-
-Gitlib throws an exception when something seems wrong. If a ``git` command returns a non-zero result, it will stop execution and throw an ``RuntimeException``.
-
-If you want to prevent this, set ``debug`` option to ``false``. This will make Repository log errors and return empty data instead of throwing exceptions.
-
-.. code-block:: php
-
- $repository = new Repository('/tmp/foo', array('debug' => false, 'logger' => $logger));
-
-.. note:: if you plan to disable debug, you should rely on logger to keep a trace of edge failing cases.
-
-Specify git command to use
-..........................
-
-You can pass option ``command`` to specify which command to use to run git calls. If you have a git binary
-located somewhere else, use this option to specify to gitlib path to your git binary:
-
-.. code-block:: php
-
- $repository = new Gitonomy\Git\Repository('/tmp/foo', array('command' => '/home/alice/bin/git'));
-
-Environment variables
-.....................
-
-Now you want to set environment variables to use to run ``git`` commands. It might be useful.
-
-.. code-block:: php
-
- $repository = new Gitonomy\Git\Repository('/tmp/foo', array('environment_variables' => array('GIT_')))
diff --git a/doc/api/revision.rst b/doc/api/revision.rst
deleted file mode 100644
index 8ffc3289..00000000
--- a/doc/api/revision.rst
+++ /dev/null
@@ -1,28 +0,0 @@
-Revision
-========
-
-To get a revision from a *Repository* object:
-
-.. code-block:: php
-
- $revision = $repository->getRevision('master@{2 days ago}');
-
-Getting the log
----------------
-
-You can access a *Log* object starting from a revision using the *getLog*
-method. This method takes two parameters: *offset* and *limit*:
-
-.. code-block:: php
-
- // Returns 100 lasts commits
- $log = $revision->getLog(null, 100);
-
-Resolve a revision
-------------------
-
-To resolve a revision to a commit:
-
-.. code-block:: php
-
- $commit = $revision->getCommit();
diff --git a/doc/api/tree.rst b/doc/api/tree.rst
deleted file mode 100644
index 4641a9f3..00000000
--- a/doc/api/tree.rst
+++ /dev/null
@@ -1,54 +0,0 @@
-Tree and files
-==============
-
-To organize folders, git uses trees. In gitlib, those trees are represented
-via *Tree* object.
-
-To get the root tree associated to a commit, use the *getTree* method on the
-commit object:
-
-.. code-block:: php
-
- $tree = $commit->getTree();
-
-This tree is the entry point of all of your files.
-
-The main method for a tree is the *getEntries* method. This method will
-return an array, indexed by name. Each of those elements will be the entry mode
-and the entry object.
-
-Let's understand how it works with a concrete example:
-
-.. code-block:: php
-
- function displayTree(Tree $tree, $indent = 0)
- {
- $indent = str_repeat(' ', $indent);
- foreach ($tree->getEntries() as $name => $data) {
- list($mode, $entry) = $data;
- if ($entry instanceof Tree) {
- echo $indent.$name."/\n";
- displayTree($tree, $indent + 1);
- } else {
- echo $indent.$name."\n";
- }
- }
- }
-
- displayTree($commit->getTree());
-
-This method will recursively display all entries of a tree.
-
-Resolve a path
---------------
-
-To access directly a sub-file, the easier is probably to use the *resolvePath*
-method.
-
-An example:
-
-.. code-block:: php
-
- $source = $tree->resolvePath('src/Gitonomy/Git');
-
- $source instanceof Tree;
diff --git a/doc/api/workingcopy.rst b/doc/api/workingcopy.rst
deleted file mode 100644
index 4b652230..00000000
--- a/doc/api/workingcopy.rst
+++ /dev/null
@@ -1,45 +0,0 @@
-Working copy
-============
-
-Working copy is the folder associated to a git repository. In *gitlib*, you
-can access this object using the *getWorkingCopy* on a *Repository* object:
-
-.. code-block:: php
-
- $repo = new Repository('/path/to/working-dir');
- $wc = $repo->getWorkingCopy();
-
-Checkout a revision
--------------------
-
-You can checkout any revision using *checkout* method. You can also pass a
-second argument, which will be passed as argument with ``-b``:
-
-.. code-block:: php
-
- // git checkout master
- $wc->checkout('master');
-
- // git checkout origin/master -b master
- $wc->checkout('origin/master', 'master');
-
-You can also pass a *Reference* or a *Commit*.
-
-Staged modifications
---------------------
-
-You can get a diff of modifications pending in staging area. To get the ``Diff`` object,
-call method ``getDiffStaged()``:
-
-.. code-block:: php
-
- $diff = $wc->getDiffStaged();
-
-Pending modifications
----------------------
-
-You can get pending modifications on tracked files by calling method ``getDiffPending()``:
-
-.. code-block:: php
-
- $diff = $wc->getDiffPending();
diff --git a/doc/blame.md b/doc/blame.md
new file mode 100644
index 00000000..80816c9b
--- /dev/null
+++ b/doc/blame.md
@@ -0,0 +1,58 @@
+Blaming files
+=============
+
+Line-per-line iteration
+-----------------------
+
+To iterate on lines of a blame:
+
+```php
+$blame = $repository->getBlame('master', 'README.md');
+
+foreach ($blame->getLines() as $lineNumber => $line) {
+ $commit = $line->getCommit();
+ echo $lineNumber.': '.$line->getContent().' - '.$commit->getAuthorName().PHP_EOL;
+}
+```
+
+The *getLines* method returns an array indexed starting from 1.
+
+As you can see, you can access the commit object related to the line you
+are iterating on.
+
+If you want to access directly a line:
+
+```php
+$line = $blame->getLine(32);
+```
+
+The Line object
+---------------
+
+LineObject represents an item of the blame file. It is composed of those
+informations:
+
+```php
+$line->getCommit(); // returns a Commit
+$line->getContent(); // returns text
+
+// you can access author from commmit:
+$author = $line->getCommit()->getAuthorName();
+```
+
+Group reading by commit
+-----------------------
+
+If you plan to display it, you'll probably need a version where lines
+from same commit are grouped.
+
+To do so, use the *getGroupedLines* method that will return an array
+like this:
+
+```php
+$blame = array(
+ array(Commit, array(1 => Line, 2 => Line, 3 => Line)),
+ array(Commit, array(4 => Line)),
+ array(Commit, array(5 => Line, 6 => Line))
+)
+```
diff --git a/doc/api/blob.rst b/doc/blob.md
similarity index 51%
rename from doc/api/blob.rst
rename to doc/blob.md
index 78d36c25..2ed3bd41 100644
--- a/doc/api/blob.rst
+++ b/doc/blob.md
@@ -2,43 +2,43 @@ Blob
====
In git, a blob represents a file content. You can't access the file name
-directly from the *Blob* object; the filename information is stored within
-the tree, not in the blob.
+directly from the *Blob* object; the filename information is stored
+within the tree, not in the blob.
-It means that for git, two files with different names but same content will
-have the same hash.
+It means that for git, two files with different names but same content
+will have the same hash.
To access a repository *Blob*, you need the hash identifier:
-.. code-block:: php
-
- $repository = new Gitonomy\Git\Repository('/path/to/repository');
- $blob = $repository->getBlob('a7c8d2b4');
+```php
+$repository = new Gitonomy\Git\Repository('/path/to/repository');
+$blob = $repository->getBlob('a7c8d2b4');
+```
Get content
-----------
To get content from a *Blob* object:
-.. code-block:: php
-
- echo $blob->getContent();
+```php
+echo $blob->getContent();
+```
File informations
-----------------
To get mimetype of a *Blob* object using finfo extension:
-.. code-block:: php
-
- echo $blob->getMimetype();
+```php
+echo $blob->getMimetype();
+```
You can also test if *Blob* is a text of a binary file:
-.. code-block:: php
-
- if ($blob->isText()) {
- echo $blob->getContent(), "\n";
- } elseif ($blob->isBinary()) {
- echo "File is binary\n";
- }
+```php
+if ($blob->isText()) {
+ echo $blob->getContent(), PHP_EOL;
+} elseif ($blob->isBinary()) {
+ echo 'File is binary', PHP_EOL;
+}
+```
diff --git a/doc/branch.md b/doc/branch.md
new file mode 100644
index 00000000..d6e0872c
--- /dev/null
+++ b/doc/branch.md
@@ -0,0 +1,16 @@
+Branch
+======
+
+To access a *Branch*, starting from a repository object:
+
+```php
+$repository = new Gitonomy\Git\Repository('/path/to/repository');
+$branch = $repository->getReferences()->getBranch('master');
+```
+
+You can check is the branch is a local or remote one:
+
+```php
+$branch->isLocal();
+$branch->isRemote();
+```
diff --git a/doc/commit.md b/doc/commit.md
new file mode 100644
index 00000000..08e9b1e3
--- /dev/null
+++ b/doc/commit.md
@@ -0,0 +1,171 @@
+Commit
+======
+
+To access a *Commit*, starting from a repository object:
+
+```php
+$repository = new Gitonomy\Git\Repository('/path/to/repository');
+$commit = $repository->getCommit('a7c8d2b4');
+```
+
+Browsing parents
+----------------
+
+A *Commit* can have a natural number of parents:
+
+- **no parent**: it's an initial commit, the root of a tree
+- **one parent**: it means it's not a merge, just a regular commit
+- **many parents**: it's a merge-commit
+
+You have 2 methods available for accessing parents:
+
+```php
+// Access parent hashes
+$hashes = $commit->getParentHashes();
+
+// Access parent commit objects
+$commits = $commit->getParents();
+```
+
+For example, if you want to display all parents, starting from a commit:
+
+```php
+function displayLog(Gitonomy\Git\Commit $commit) {
+ echo '- '.$commit->getShortMessage().PHP_EOL;
+ foreach ($commit->getParents() as $parent) {
+ displayLog($parent);
+ }
+}
+```
+
+Notice that this function will first display all commits from first
+merged branch and then display all commits from next branch, and so on.
+
+Accessing tree
+--------------
+
+The tree object contains the reference to the files associated to a
+given commit. Every commit has one and only one tree, referencing all
+files and folders of a given state for a project. For more informations
+about the tree, see the chapter dedicated to it.
+
+To access a tree starting from a commit:
+
+```php
+// Returns the tree hash
+$tree = $commit->getTreeHash();
+
+// Returns the tree object
+$tree = $commit->getTree();
+```
+
+Author & Committer informations
+-------------------------------
+
+Each commit has two authoring informations: an author and a committer.
+The author is the creator of the modification, authoring a modification
+in the repository. The committer is responsible of introducing this
+modification to the repository.
+
+You can access informations from author and committer using those
+methods:
+
+```php
+// Author
+$commit->getAuthorName();
+$commit->getAuthorEmail();
+$commit->getAuthorDate(); // returns a DateTime object
+
+// Committer
+$commit->getCommitterName();
+$commit->getCommitterEmail();
+$commit->getCommitterDate(); // returns a DateTime object
+```
+
+Commit message and short message
+--------------------------------
+
+Each commit also has a message, associated to the modification. This
+message can be multilined.
+
+To access the message, you can use the *getMessage* method:
+
+```php
+$commit->getMessage();
+```
+
+For your convenience, this library provides a shortcut method to keep
+only the first line or first 50 characters if the first line is too
+long:
+
+```php
+$commit->getShortMessage();
+```
+
+You can customize it like this:
+
+```php
+$commit->getShortMessage(45, true, '.');
+```
+
+- The first parameter is the max length of the message.
+- The second parameter determine if the last word should be cut or
+ preserved
+- The third parameter is the separator
+
+There are also two other methods for your convenience:
+
+```php
+// The first line
+$commit->getSubjectMessage();
+
+// The body (rest of the message)
+$commit->getBodyMessage();
+```
+
+Diff of a commit
+----------------
+
+You can check the modifications introduced by a commit using the
+*getDiff* method. When you request a diff for a commit, depending of the
+number of parents, the strategy will be different:
+
+- If you have *no parent*, the diff will be the content of the tree
+- If you only have *one parent*, the diff will be between the commit
+ and his parent
+- If you have *multiple parents*, the diff will be the difference
+ between the commit and the first common ancestor of all parents
+
+For more informations about the diff API, read the related chapter.
+
+To access the *Diff* object of a commit, use the method *getDiff*:
+
+```php
+$diff = $commit->getDiff();
+```
+
+Last modification of a file
+---------------------------
+
+To know the last modification of a file, you can use the
+*getLastModification* method on a commit.
+
+Here is a very straightforward example:
+
+```php
+$last = $commit->getLastModification('README');
+
+echo 'Last README modification'.PHP_EOL;
+echo ' Author: '.$last->getAuthorName().PHP_EOL;
+echo ' Date: '.$last->getAuthorDate()->format('d/m/Y').PHP_EOL;
+echo ' Message: '.$last->getMessage();
+```
+
+Find every branches containing a commit
+---------------------------------------
+
+```php
+$branches = $commit->getIncludingBranches($includeLocalBranches, $includeRemoteBranches);
+$localBranches = $commit->getIncludingBranches(true, false);
+$remoteBranches = $commit->getIncludingBranches(false, true);
+```
diff --git a/doc/debug.rst b/doc/debug.rst
deleted file mode 100644
index 704fd353..00000000
--- a/doc/debug.rst
+++ /dev/null
@@ -1,22 +0,0 @@
-Debug-mode
-==========
-
-gitlib offers a debug mode, to make you see edge-cases of your usage. This is called
-debug-mode.
-
-Debug-mode is enabled by default. If you disable it, gitlib will behave differently:
-
-* when an error is met during execution, gitlib will try to minimize it, to not block
- execution flow. Errors will still be reporter in logger.
-* logs will be more verbose. They will contain every output, every return code, every
- possible information to ease debugging.
-
-If you want to disable exceptions and try to minimize as much as possible errors, pass
-``false`` when construction a repository:
-
-.. code-block:: php
-
- $repository = new Gitonomy\Git\Repository($path'/tmp/repo', $debug = false)
-
-``$debug`` argument should be available in every method you can use to create a
-repository.
diff --git a/doc/development.rst b/doc/development.rst
deleted file mode 100644
index 986b0868..00000000
--- a/doc/development.rst
+++ /dev/null
@@ -1,24 +0,0 @@
-Developing gitlib
-=================
-
-If you plan to contribute to gitlib, here are few things you should know:
-
-Documentation generation
-::::::::::::::::::::::::
-
-Documentation is generated using Sphinx (restructured text). Configuration file
-is located in https://github.com/gitonomy/website/blob/master/bin/conf.py
-
-You will need to fetch vendor modules for PHP blocks especially. If you really
-want to generate it, install the website project locally and hack into it.
-
-Test against different git versions
-:::::::::::::::::::::::::::::::::::
-
-A script ``test-git-version.sh`` is available in repository to test gitlib against
-many git versions.
-
-This script is not usable on Travis-CI, they would hate me for this. It creates
-a local cache to avoid fetching from Github and compiling if already compiled.
-
-Use it at your own risk, it's still under experiment.
diff --git a/doc/diff.md b/doc/diff.md
new file mode 100644
index 00000000..2ce7870a
--- /dev/null
+++ b/doc/diff.md
@@ -0,0 +1,104 @@
+Computing diff
+==============
+
+Even if git is a diff-less storage engine, it's possible to compute
+them.
+
+To compute a diff in git, you need to specify a *revision*. This
+revision can be a commit (*2bc7a8*) or a range (*2bc7a8..ff4c21b*).
+
+For more informations about git revisions: *man gitrevisions*.
+
+When you have decided the revision you want and have your *Repository*
+object, you can call the *getDiff* method on the repository:
+
+```php
+$diff = $repository->getDiff('master@{2 days ago}..master');
+```
+
+You can also access it from a *Log* object:
+
+```php
+$log = $repository->getLog('master@{2 days ago}..master');
+$diff = $log->getDiff();
+```
+
+Iterating a diff
+----------------
+
+When you have a *Diff* object, you can iterate over files using method
+*getFiles()*. This method returns a list of *File* objects, who
+represents the modifications for a single file.
+
+```php
+$files = $diff->getFiles();
+echo sprintf('%s files modified%s', count($files), PHP_EOL);
+
+foreach ($files as $fileDiff) {
+ echo sprintf('Old name: (%s) %s%s', $fileDiff->getOldMode(), $fileDiff->getOldName(), PHP_EOL);
+ echo sprintf('New name: (%s) %s%s', $fileDiff->getNewMode(), $fileDiff->getNewName(), PHP_EOL);
+}
+```
+
+The File object
+---------------
+
+Here is an exhaustive list of the *File* class methods:
+
+```php
+$file->getOldName();
+$file->getNewName();
+$file->getOldDiff();
+$file->getNewDiff();
+
+$file->isCreation();
+$file->isDeletion();
+$file->isModification();
+
+$file->isRename();
+$file->isChangeMode();
+
+$file->getAdditions(); // Number of added lines
+$file->getDeletions(); // Number of deleted lines
+
+$file->isBinary(); // Binary files have no "lines"
+
+$file->getChanges(); // See next chapter
+```
+
+The FileChange object
+---------------------
+
+> **note**
+>
+> This part of API is not very clean, very consistent. If you have any
+> idea or suggestion on how to enhance this, your comment would be
+> appreciated.
+
+A *File* object is composed of many changes. For each of those changes,
+a *FileChange* object is associated.
+
+To access changes from a file, use the *getChanges* method:
+
+```php
+$changes = $file->getChanges();
+foreach ($changes as $change) {
+ foreach ($lines as $data) {
+ list ($type, $line) = $data;
+ if ($type === FileChange::LINE_CONTEXT) {
+ echo ' '.$line.PHP_EOL;
+ } elseif ($type === FileChange::LINE_ADD) {
+ echo '+'.$line.PHP_EOL;
+ } else {
+ echo '-'.$line.PHP_EOL;
+ }
+ }
+}
+```
+
+To get line numbers, use the range methods:
+
+```php
+echo sprintf('Previously from line %s to %s%s', $change->getOldRangeStart(), $change->getOldRangeEnd(), PHP_EOL);
+echo sprintf('Now from line %s to %s%s', $change->getNewRangeStart(), $change->getNewRangeEnd(), PHP_EOL);
+```
diff --git a/doc/hooks.md b/doc/hooks.md
new file mode 100644
index 00000000..eb5c8ab4
--- /dev/null
+++ b/doc/hooks.md
@@ -0,0 +1,80 @@
+Hooks
+=====
+
+It's possible to define custom hooks on any repository with git. Those
+hooks are located in the *.git/hooks* folder.
+
+Those files need to be executable. For convenience, gitlib will set them
+to *777*.
+
+With *gitlib*, you can manage hooks over a repository using the *Hooks*
+object.
+
+To access it from a repository, use the *getHooks* method on a
+*Repository* object:
+
+```php
+$hooks = $repository->getHooks();
+```
+
+Reading hooks
+-------------
+
+To read the content of a hook, use the *get* method like this:
+
+```php
+$content = $hooks->get('pre-receive'); // returns a string
+```
+
+If the hook does not exist, an exception will be thrown
+(*InvalidArgumentException*).
+
+You can test if a hook is present using the method *has*:
+
+```php
+$hooks->has('pre-receive'); // a boolean indicating presence
+```
+
+Inserting hooks
+---------------
+
+You can modify a hook in two different ways: creating a new file or
+using a symlink.
+
+To create the hook using a symlink:
+
+```php
+$hooks->setSymlink('pre-receive', '/path/to/file-to-link');
+```
+
+If the hook already exist, a *LogicException* will be thrown. If an
+error occured during symlink creation, a *RuntimeException* will be
+thrown.
+
+If you want to directly create a new file in hooks directory, use the
+method *set*. This method will create a new file, put content in it and
+make it executable:
+
+```php
+$content = <<set('pre-receive', $content);
+```
+
+If the hook already exists, a *LogicException* will be thrown.
+
+Removing hooks
+--------------
+
+To remove a hook from a repository, use the function *remove*:
+
+```php
+$hooks->remove('pre-receive');
+```
diff --git a/doc/index.rst b/doc/index.rst
deleted file mode 100644
index 919e91c5..00000000
--- a/doc/index.rst
+++ /dev/null
@@ -1,59 +0,0 @@
-gitlib - library to manipulate git
-==================================
-
-gitlib requires PHP 5.3 and class autoloading (PSR-0) to work properly. Internally, it relies on ``git`` method calls
-to fetch informations from repository.
-
-.. code-block:: php
-
- use Gitonomy\Git\Repository;
-
- $repository = new Repository('/path/to/repository');
-
- foreach ($repository->getReferences()->getBranches() as $branch) {
- echo "- ".$branch->getName();
- }
-
- $repository->run('fetch', array('--all'));
-
-
-Reference
----------
-
-.. toctree::
- :maxdepth: 1
-
- api/admin
- api/repository
- api/hooks
- api/workingcopy
- api/commit
- api/blame
- api/blob
- api/branch
- api/tree
- api/log
- api/diff
- api/references
- api/revision
-
-
-Documentation
--------------
-
-.. toctree::
- :maxdepth: 2
-
- installation
- debug
- development
-
-Missing features
-----------------
-
-Some major features are still missing from gitlib:
-
-* Remotes
-* Submodules
-
-If you want to run git commands on repository, call method ``Repository::run`` with method and arguments.
diff --git a/doc/installation.rst b/doc/installation.rst
deleted file mode 100644
index 06d36d21..00000000
--- a/doc/installation.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-Installation of gitlib
-======================
-
-Autoloading
-:::::::::::
-
-gitlib relies on class autoloading. It does not require any additional setup.
-
-Using composer
-::::::::::::::
-
-Edit your ``composer.json`` file and add ``gitonomy/gitlib`` in section ``require``:
-
-.. code-block:: json
-
- {
- "require": {
- "gitonomy/gitlib": "^1.0"
- }
- }
diff --git a/doc/log.md b/doc/log.md
new file mode 100644
index 00000000..4dc5a465
--- /dev/null
+++ b/doc/log.md
@@ -0,0 +1,55 @@
+Getting log history
+===================
+
+Crawling manually commits and parents to browse history is surely a good
+solution. But when it comes to ordering them or aggregate them from
+multiple branches, we tend to use `git log`.
+
+To get a *Log* object from a repository:
+
+```php
+$log = $repository->getLog();
+```
+
+You can pass four arguments to *getLog* method:
+
+```php
+// Global log for repository
+$log = $repository->getLog();
+
+// Log for master branch
+$log = $repository->getLog('master');
+
+// Returns last 10 commits on README file
+$log = $repository->getLog('master', 'README', 0, 10);
+
+// Returns last 10 commits on README or UPGRADE files
+$log = $repository->getLog('master', ['README', 'UPGRADE'], 0, 10);
+```
+
+Counting
+--------
+
+If you want to count overall commits, without offset or limit, use the
+*countCommits* method:
+
+```php
+echo sprintf('This log contains %s commits%s', $log->countCommits(), PHP_EOL);
+
+// Countable interface
+echo sprintf('This log contains %s commits%s', count($log), PHP_EOL);
+```
+
+Offset and limit
+----------------
+
+Use those methods:
+
+```php
+$log->setOffset(32);
+$log->setLimit(40);
+
+// or read it:
+$log->getOffset();
+$log->getLimit();
+```
diff --git a/doc/references.md b/doc/references.md
new file mode 100644
index 00000000..64430929
--- /dev/null
+++ b/doc/references.md
@@ -0,0 +1,92 @@
+Tags and branches
+=================
+
+Accessing tags and branches
+---------------------------
+
+With *gitlib*, you can access them via the *ReferenceBag* object. To get
+this object from a *Repository*, use the *getReferences* method:
+
+```php
+$references = $repository->getReferences();
+```
+
+First, you can test existence of tags and branches like this:
+
+```php
+if ($references->hasBranch('master') && $references->hasTag('0.1')) {
+ echo 'Good start!'.PHP_EOL;
+}
+```
+
+If you want to access all branches or all tags:
+
+```php
+$branches = $references->getBranches();
+$localBranches = $references->getLocalBranches();
+$remoteBranches = $references->getRemoteBranches();
+$tags = $references->getTags();
+$all = $references->getAll();
+```
+
+To get a given branch or tag, call *getBranch* or *getTag* on the
+*ReferenceBag*. Those methods return *Branch* and *Tag* objects:
+
+```php
+$master = $references->getBranch('master');
+$feat123 = $references->getLocalBranch('feat123');
+$feat456 = $references->getRemoteBranch('origin/feat456');
+$v0_1 = $references->getTag('0.1');
+```
+
+If the reference cannot be resolved, a *ReferenceNotFoundException* will
+be thrown.
+
+On each of those objects, you can access those informations:
+
+```php
+// Get the associated commit
+$commit = $master->getCommit();
+
+// Get the commit hash
+$hash = $master->getCommitHash();
+
+// Get the last modification
+$lastModification = $master->getLastModification();
+```
+
+Create and delete reference
+---------------------------
+
+You can create new tags and branches on repository, using helper methods
+on ReferenceBag object:
+
+```php
+// create a branch
+$references = $repository->getReferences();
+$branch = $references->createBranch('foobar', 'a8b7e4...'); // commit to reference
+
+// create a tag
+$references = $repository->getReferences();
+$tag = $references->createTag('0.3', 'a8b7e4...'); // commit to reference
+
+// delete a branch or a tag
+$branch->delete();
+```
+
+Resolution from a commit
+------------------------
+
+To resolve a branch or a commit from a commit, you can use the
+*resolveTags* and *resolveBranches* methods on it:
+
+```php
+$branches = $references->resolveBranches($commit);
+$tags = $references->resolveTags($commit);
+
+// Resolve branches and tags
+$all = $references->resolve($commit);
+```
+
+You can pass a *Commit* object or a hash to the method, gitlib will
+handle it.
diff --git a/doc/repository.md b/doc/repository.md
new file mode 100644
index 00000000..05b4a4e7
--- /dev/null
+++ b/doc/repository.md
@@ -0,0 +1,147 @@
+Repository methods
+==================
+
+Creating a *Repository* object is possible, providing a *path* argument
+to the constructor:
+
+```php
+$repository = new Repository('/path/to/repo');
+```
+
+Repository options
+------------------
+
+The constructor of Repository takes an additional parameter: `$options`.
+This parameter can be used used to tune behavior of library.
+
+Available options are:
+
+- **debug** (default: true): Enables exception when edge cases are met
+- **environment\_variables**: (default: none) An array of environment
+ variables to be set in sub-process
+- **logger**: (default: none) Logger to use for reporting of execution
+ (a `Psr\Log\LoggerInterface`)
+- **command**: (default: `git`) Specify command to execute to run git
+- **working\_dir**: If you are using multiple working directories,
+ this option is for you
+
+An example:
+
+```php
+$repository = new Repository('/path/to/repo', [
+ 'debug' => true,
+ 'logger' => new Monolog\Logger(),
+]);
+```
+
+Test if a repository is bare
+----------------------------
+
+On a *Repository* object, you can call method *isBare* to test if your
+repository is bare or not:
+
+```php
+$repository->isBare();
+```
+
+Compute size of a repository
+----------------------------
+
+To know how much size a repository is using on your drive, you can use
+`getSize` method on a *Repository* object.
+
+> **warning**
+>
+> This command was only tested with linux.
+
+The returned size is in kilobytes:
+
+```php
+$size = $repository->getSize();
+
+echo 'Your repository size is '.$size.'KB';
+```
+
+Access HEAD
+-----------
+
+`HEAD` represents in git the version you are working on (in working
+tree). Your `HEAD` can be attached (using a reference) or detached
+(using a commit).
+
+```php
+$head = $repository->getHead(); // Commit or Reference
+$head = $repository->getHeadCommit(); // Commit
+
+if ($repository->isHeadDetached()) {
+ echo 'Sorry man'.PHP_EOL;
+}
+```
+
+Options for repository
+----------------------
+
+### Logger
+
+If you are developing, you may appreciate to have a logger inside
+repository, telling you every executed command.
+
+You call method `setLogger` as an option on repository creation:
+
+```php
+$repository->setLogger(new Monolog\Logger('repository'));
+
+$repository->run('fetch', ['--all']);
+```
+
+You can also specify as an option on repository creation:
+
+```php
+$logger = new MonologLogger('repository');
+$repository = new Repository('/path/foo', ['logger' => $logger]);
+$repository->run('fetch', ['--all']);
+```
+
+This will output:
+
+```
+info run command: fetch "--all"
+debug last command (fetch) duration: 23.24ms
+debug last command (fetch) return code: 0
+debug last command (fetch) output: Fetching origin
+```
+
+### Disable debug-mode
+
+Gitlib throws an exception when something seems wrong. If a `git` command exits
+with a non-zero code, then execution will be stopped, and a `RuntimeException`
+will be thrown. If you want to prevent this, set the `debug` option to` false`.
+This will make `Repository` log errors and return empty data instead of
+throwing exceptions.
+
+```php
+$repository = new Repository('/tmp/foo', ['debug' => false, 'logger' => $logger]);
+```
+
+> **note**
+>
+> If you plan to disable debug, you should rely on the logger to keep a trace
+> of the failing cases.
+
+### Specify git command to use
+
+You can pass the option `command` to specify which command to use to run git
+calls. If you have a git binary located somewhere else, use this option to
+specify to gitlib path to your git binary:
+
+```php
+$repository = new Gitonomy\Git\Repository('/tmp/foo', ['command' => '/home/alice/bin/git']);
+```
+
+### Environment variables
+
+It is possible to send environment variables to the `git` commands.
+
+```php
+$repository = new Gitonomy\Git\Repository('/tmp/foo', ['environment_variables' => ['GIT_']])
+```
diff --git a/doc/revision.md b/doc/revision.md
new file mode 100644
index 00000000..81370c80
--- /dev/null
+++ b/doc/revision.md
@@ -0,0 +1,28 @@
+Revision
+========
+
+To get a revision from a *Repository* object:
+
+```php
+$revision = $repository->getRevision('master@{2 days ago}');
+```
+
+Getting the log
+---------------
+
+You can access a *Log* object starting from a revision using the
+*getLog* method. This method takes two parameters: *offset* and *limit*:
+
+```php
+// Returns 100 lasts commits
+$log = $revision->getLog(null, 100);
+```
+
+Resolve a revision
+------------------
+
+To resolve a revision to a commit:
+
+```php
+$commit = $revision->getCommit();
+```
diff --git a/doc/tree.md b/doc/tree.md
new file mode 100644
index 00000000..0c5c8ef7
--- /dev/null
+++ b/doc/tree.md
@@ -0,0 +1,54 @@
+Tree and files
+==============
+
+To organize folders, git uses trees. In gitlib, those trees are
+represented via *Tree* object.
+
+To get the root tree associated to a commit, use the *getTree* method on
+the commit object:
+
+```php
+$tree = $commit->getTree();
+```
+
+This tree is the entry point of all of your files.
+
+The main method for a tree is the *getEntries* method. This method will
+return an array, indexed by name. Each of those elements will be the
+entry mode and the entry object.
+
+Let's understand how it works with a concrete example:
+
+```php
+function displayTree(Tree $tree, $indent = 0)
+{
+ $indent = str_repeat(' ', $indent);
+ foreach ($tree->getEntries() as $name => $data) {
+ list($mode, $entry) = $data;
+ if ($entry instanceof Tree) {
+ echo $indent.$name.'/'.PHP_EOL;
+ displayTree($tree, $indent + 1);
+ } else {
+ echo $indent.$name.PHP_EOL;
+ }
+ }
+}
+
+displayTree($commit->getTree());
+```
+
+This method will recursively display all entries of a tree.
+
+Resolve a path
+--------------
+
+To access directly a sub-file, the easier is probably to use the
+*resolvePath* method.
+
+An example:
+
+```php
+$source = $tree->resolvePath('src/Gitonomy/Git');
+
+$source instanceof Tree;
+```
diff --git a/doc/workingcopy.md b/doc/workingcopy.md
new file mode 100644
index 00000000..4ecea6c1
--- /dev/null
+++ b/doc/workingcopy.md
@@ -0,0 +1,47 @@
+Working copy
+============
+
+Working copy is the folder associated to a git repository. In *gitlib*,
+you can access this object using the *getWorkingCopy* on a *Repository*
+object:
+
+```php
+$repo = new Repository('/path/to/working-dir');
+$wc = $repo->getWorkingCopy();
+```
+
+Checkout a revision
+-------------------
+
+You can checkout any revision using *checkout* method. You can also pass
+a second argument, which will be passed as argument with `-b`:
+
+```php
+// git checkout master
+$wc->checkout('master');
+
+// git checkout origin/master -b master
+$wc->checkout('origin/master', 'master');
+```
+
+You can also pass a *Reference* or a *Commit*.
+
+Staged modifications
+--------------------
+
+You can get a diff of modifications pending in staging area. To get the
+`Diff` object, call method `getDiffStaged()`:
+
+```php
+$diff = $wc->getDiffStaged();
+```
+
+Pending modifications
+---------------------
+
+You can get pending modifications on tracked files by calling method
+`getDiffPending()`:
+
+```php
+$diff = $wc->getDiffPending();
+```
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 36964385..77fe6238 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,21 +1,29 @@
-
-
-
+
- tests/Gitonomy/Git/Tests
+ ./tests/Gitonomy/Git/Tests
-
+
+
+ ./src/Gitonomy/Git
+
+
diff --git a/src/Gitonomy/Git/Admin.php b/src/Gitonomy/Git/Admin.php
index 7b64cd77..7b32794c 100644
--- a/src/Gitonomy/Git/Admin.php
+++ b/src/Gitonomy/Git/Admin.php
@@ -67,6 +67,30 @@ public static function isValidRepository($url, array $options = [])
return $process->isSuccessFul();
}
+ /**
+ * Checks the validity of a git repository url without cloning it and
+ * check if a certain branch exists in that repository.
+ *
+ * This will use the `ls-remote` command of git against the given url.
+ * Usually, this command returns 0 when successful, and 128 when the
+ * repository is not found.
+ *
+ * @param string $url url of repository to check
+ * @param string $branchName name of branch to check
+ * @param array $options options for Repository creation
+ *
+ * @return bool true if url is valid and branch exists
+ */
+ public static function isValidRepositoryAndBranch($url, $branchName, array $options = [])
+ {
+ $process = static::getProcess('ls-remote', ['--heads', $url, $branchName], $options);
+
+ $process->run();
+ $processOutput = $process->getOutput();
+
+ return $process->isSuccessFul() && strpos($processOutput, $branchName) !== false;
+ }
+
/**
* Clone a repository to a local path.
*
diff --git a/src/Gitonomy/Git/Blame.php b/src/Gitonomy/Git/Blame.php
index 17d8bfe7..dec0773b 100644
--- a/src/Gitonomy/Git/Blame.php
+++ b/src/Gitonomy/Git/Blame.php
@@ -108,9 +108,7 @@ public function getGroupedLines()
}
/**
- * Returns all lines of the blame.
- *
- * @return array
+ * @return Line[] All lines of the blame.
*/
public function getLines()
{
@@ -139,6 +137,7 @@ public function getLines()
/**
* @return int
*/
+ #[\ReturnTypeWillChange]
public function count()
{
return count($this->getLines());
diff --git a/src/Gitonomy/Git/Blob.php b/src/Gitonomy/Git/Blob.php
index 10150935..dfe885fe 100644
--- a/src/Gitonomy/Git/Blob.php
+++ b/src/Gitonomy/Git/Blob.php
@@ -19,6 +19,11 @@
*/
class Blob
{
+ /**
+ * @var int Size that git uses to look for NULL byte: https://git.kernel.org/pub/scm/git/git.git/tree/xdiff-interface.c?h=v2.44.0#n193
+ */
+ private const FIRST_FEW_BYTES = 8000;
+
/**
* @var Repository
*/
@@ -39,6 +44,11 @@ class Blob
*/
protected $mimetype;
+ /**
+ * @var bool
+ */
+ protected $text;
+
/**
* @param Repository $repository Repository where the blob is located
* @param string $hash Hash of the blob
@@ -58,9 +68,9 @@ public function getHash()
}
/**
- * Returns content of the blob.
- *
* @throws ProcessException Error occurred while getting content of blob
+ *
+ * @return string Content of the blob.
*/
public function getContent()
{
@@ -89,6 +99,9 @@ public function getMimetype()
/**
* Determines if file is binary.
*
+ * Uses the same check that git uses to determine if a file is binary or not
+ * https://git.kernel.org/pub/scm/git/git.git/tree/xdiff-interface.c?h=v2.44.0#n193
+ *
* @return bool
*/
public function isBinary()
@@ -99,10 +112,17 @@ public function isBinary()
/**
* Determines if file is text.
*
+ * Uses the same check that git uses to determine if a file is binary or not
+ * https://git.kernel.org/pub/scm/git/git.git/tree/xdiff-interface.c?h=v2.44.0#n193
+ *
* @return bool
*/
public function isText()
{
- return (bool) preg_match('#^text/|^application/xml#', $this->getMimetype());
+ if (null === $this->text) {
+ $this->text = !str_contains(substr($this->getContent(), 0, self::FIRST_FEW_BYTES), chr(0));
+ }
+
+ return $this->text;
}
}
diff --git a/src/Gitonomy/Git/Commit.php b/src/Gitonomy/Git/Commit.php
index 10141bec..573d3b52 100644
--- a/src/Gitonomy/Git/Commit.php
+++ b/src/Gitonomy/Git/Commit.php
@@ -16,6 +16,7 @@
use Gitonomy\Git\Exception\InvalidArgumentException;
use Gitonomy\Git\Exception\ProcessException;
use Gitonomy\Git\Exception\ReferenceNotFoundException;
+use Gitonomy\Git\Reference\Branch;
use Gitonomy\Git\Util\StringHelper;
/**
@@ -35,8 +36,8 @@ class Commit extends Revision
/**
* Constructor.
*
- * @param Gitonomy\Git\Repository $repository Repository of the commit
- * @param string $hash Hash of the commit
+ * @param Repository $repository Repository of the commit
+ * @param string $hash Hash of the commit
*/
public function __construct(Repository $repository, $hash, array $data = [])
{
@@ -61,7 +62,7 @@ public function setData(array $data)
*/
public function getDiff()
{
- $args = ['-r', '-p', '-m', '-M', '--no-commit-id', '--full-index', $this->revision];
+ $args = ['-r', '-p', '--raw', '-m', '-M', '--no-commit-id', '--full-index', $this->revision];
$diff = Diff::parse($this->repository->run('diff-tree', $args));
$diff->setRepository($this->repository);
@@ -91,6 +92,8 @@ public function getShortHash()
/**
* Returns a fixed-with short hash.
+ *
+ * @return string Short hash
*/
public function getFixedShortHash($length = 6)
{
@@ -100,7 +103,7 @@ public function getFixedShortHash($length = 6)
/**
* Returns parent hashes.
*
- * @return array An array of SHA1 hashes
+ * @return string[] An array of SHA1 hashes
*/
public function getParentHashes()
{
@@ -110,7 +113,7 @@ public function getParentHashes()
/**
* Returns the parent commits.
*
- * @return array An array of Commit objects
+ * @return Commit[] An array of Commit objects
*/
public function getParents()
{
@@ -132,6 +135,9 @@ public function getTreeHash()
return $this->getData('treeHash');
}
+ /**
+ * @return Tree
+ */
public function getTree()
{
return $this->getData('tree');
@@ -184,7 +190,7 @@ public function getShortMessage($length = 50, $preserve = false, $separator = '.
/**
* Resolves all references associated to this commit.
*
- * @return array An array of references (Branch, Tag, Squash)
+ * @return Reference[] An array of references (Branch, Tag, Squash)
*/
public function resolveReferences()
{
@@ -197,7 +203,7 @@ public function resolveReferences()
* @param bool $local set true to try to locate a commit on local repository
* @param bool $remote set true to try to locate a commit on remote repository
*
- * @return array An array of Reference\Branch
+ * @return Reference[]|Branch[] An array of Reference\Branch
*/
public function getIncludingBranches($local = true, $remote = true)
{
@@ -374,9 +380,9 @@ private function getData($name)
array_shift($lines);
array_shift($lines);
- $data['bodyMessage'] = implode("\n", $lines);
+ $this->data['bodyMessage'] = implode("\n", $lines);
- return $data['bodyMessage'];
+ return $this->data['bodyMessage'];
}
$parser = new Parser\CommitParser();
diff --git a/src/Gitonomy/Git/CommitReference.php b/src/Gitonomy/Git/CommitReference.php
index bb20bc3c..7be3d89c 100644
--- a/src/Gitonomy/Git/CommitReference.php
+++ b/src/Gitonomy/Git/CommitReference.php
@@ -14,6 +14,9 @@
class CommitReference
{
+ /**
+ * @var string
+ */
private $hash;
public function __construct($hash)
diff --git a/src/Gitonomy/Git/Diff/Diff.php b/src/Gitonomy/Git/Diff/Diff.php
index faafbcba..7737ee8f 100644
--- a/src/Gitonomy/Git/Diff/Diff.php
+++ b/src/Gitonomy/Git/Diff/Diff.php
@@ -23,7 +23,7 @@
class Diff
{
/**
- * @var array
+ * @var File[]
*/
protected $files;
@@ -62,18 +62,10 @@ public function setRepository(Repository $repository)
}
}
- /**
- * @return array
- */
- public function getRevisions()
- {
- return $this->revisions;
- }
-
/**
* Get list of files modified in the diff's revision.
*
- * @return array An array of Diff\File objects
+ * @return File[] An array of Diff\File objects
*/
public function getFiles()
{
diff --git a/src/Gitonomy/Git/Diff/File.php b/src/Gitonomy/Git/Diff/File.php
index f7099769..bfca155c 100644
--- a/src/Gitonomy/Git/Diff/File.php
+++ b/src/Gitonomy/Git/Diff/File.php
@@ -55,7 +55,7 @@ class File
protected $isBinary;
/**
- * @var array An array of FileChange objects
+ * @var FileChange[] An array of FileChange objects
*/
protected $changes;
@@ -215,6 +215,9 @@ public function isBinary()
return $this->isBinary;
}
+ /**
+ * @return FileChange[]
+ */
public function getChanges()
{
return $this->changes;
@@ -236,6 +239,9 @@ public function toArray()
];
}
+ /**
+ * @return File
+ */
public static function fromArray(array $array)
{
$file = new self($array['old_name'], $array['new_name'], $array['old_mode'], $array['new_mode'], $array['old_index'], $array['new_index'], $array['is_binary']);
@@ -272,6 +278,10 @@ public function getOldBlob()
throw new \LogicException('Can\'t return old Blob on a creation');
}
+ if ($this->oldIndex === '') {
+ throw new \RuntimeException('Index is missing to return Blob object.');
+ }
+
return $this->repository->getBlob($this->oldIndex);
}
@@ -285,6 +295,10 @@ public function getNewBlob()
throw new \LogicException('Can\'t return new Blob on a deletion');
}
+ if ($this->newIndex === '') {
+ throw new \RuntimeException('Index is missing to return Blob object.');
+ }
+
return $this->repository->getBlob($this->newIndex);
}
}
diff --git a/src/Gitonomy/Git/Diff/FileChange.php b/src/Gitonomy/Git/Diff/FileChange.php
index 898bbb48..cca18dc6 100644
--- a/src/Gitonomy/Git/Diff/FileChange.php
+++ b/src/Gitonomy/Git/Diff/FileChange.php
@@ -24,6 +24,15 @@ class FileChange
protected $rangeNewCount;
protected $lines;
+ /**
+ * @param int $rangeOldStart
+ * @param int $rangeOldCount
+ * @param int $rangeNewStart
+ * @param int $rangeNewCount
+ * @param array $lines
+ *
+ * @return void
+ */
public function __construct($rangeOldStart, $rangeOldCount, $rangeNewStart, $rangeNewCount, $lines)
{
$this->rangeOldStart = $rangeOldStart;
@@ -33,6 +42,9 @@ public function __construct($rangeOldStart, $rangeOldCount, $rangeNewStart, $ran
$this->lines = $lines;
}
+ /**
+ * @return int
+ */
public function getCount($type)
{
$result = 0;
@@ -45,31 +57,49 @@ public function getCount($type)
return $result;
}
+ /**
+ * @return int
+ */
public function getRangeOldStart()
{
return $this->rangeOldStart;
}
+ /**
+ * @return int
+ */
public function getRangeOldCount()
{
return $this->rangeOldCount;
}
+ /**
+ * @return int
+ */
public function getRangeNewStart()
{
return $this->rangeNewStart;
}
+ /**
+ * @return int
+ */
public function getRangeNewCount()
{
return $this->rangeNewCount;
}
+ /**
+ * @return array
+ */
public function getLines()
{
return $this->lines;
}
+ /**
+ * @return array
+ */
public function toArray()
{
return [
@@ -81,6 +111,11 @@ public function toArray()
];
}
+ /**
+ * @param array $array
+ *
+ * @return self
+ */
public static function fromArray(array $array)
{
return new self($array['range_old_start'], $array['range_old_count'], $array['range_new_start'], $array['range_new_count'], $array['lines']);
diff --git a/src/Gitonomy/Git/Hooks.php b/src/Gitonomy/Git/Hooks.php
index 3c19d2c4..dd0adbe8 100644
--- a/src/Gitonomy/Git/Hooks.php
+++ b/src/Gitonomy/Git/Hooks.php
@@ -14,6 +14,7 @@
use Gitonomy\Git\Exception\InvalidArgumentException;
use Gitonomy\Git\Exception\LogicException;
+use Gitonomy\Git\Exception\RuntimeException;
/**
* Hooks collection, aggregated by repository.
@@ -23,7 +24,7 @@
class Hooks
{
/**
- * @var Gitonomy\Git\Repository
+ * @var \Gitonomy\Git\Repository
*/
protected $repository;
@@ -82,7 +83,7 @@ public function setSymlink($name, $file)
$path = $this->getPath($name);
if (false === symlink($file, $path)) {
- throw new RuntimeException(sprintf('Unable to create hook "%s"', $name, $path));
+ throw new RuntimeException(sprintf('Unable to create hook "%s" (%s)', $name, $path));
}
}
@@ -121,6 +122,9 @@ public function remove($name)
unlink($this->getPath($name));
}
+ /**
+ * @return string
+ */
protected function getPath($name)
{
return $this->repository->getGitDir().'/hooks/'.$name;
diff --git a/src/Gitonomy/Git/Log.php b/src/Gitonomy/Git/Log.php
index 13f1fb0f..47665359 100644
--- a/src/Gitonomy/Git/Log.php
+++ b/src/Gitonomy/Git/Log.php
@@ -12,6 +12,7 @@
namespace Gitonomy\Git;
+use Gitonomy\Git\Diff\Diff;
use Gitonomy\Git\Exception\ProcessException;
use Gitonomy\Git\Exception\ReferenceNotFoundException;
use Gitonomy\Git\Util\StringHelper;
@@ -136,6 +137,9 @@ public function setLimit($limit)
return $this;
}
+ /**
+ * @return Commit
+ */
public function getSingleCommit()
{
$limit = $this->limit;
@@ -151,7 +155,7 @@ public function getSingleCommit()
}
/**
- * @return array
+ * @return Commit[]
*/
public function getCommits()
{
@@ -202,6 +206,7 @@ public function getCommits()
/**
* @see Countable
*/
+ #[\ReturnTypeWillChange]
public function count()
{
return $this->countCommits();
@@ -210,6 +215,7 @@ public function count()
/**
* @see IteratorAggregate
*/
+ #[\ReturnTypeWillChange]
public function getIterator()
{
return new \ArrayIterator($this->getCommits());
diff --git a/src/Gitonomy/Git/Parser/CommitParser.php b/src/Gitonomy/Git/Parser/CommitParser.php
index 98234f6d..6fff1ea4 100644
--- a/src/Gitonomy/Git/Parser/CommitParser.php
+++ b/src/Gitonomy/Git/Parser/CommitParser.php
@@ -44,8 +44,10 @@ protected function doParse()
$this->consumeNewLine();
$this->consume('committer ');
- list($this->committerName, $this->committerEmail, $this->committerDate) = $this->consumeNameEmailDate();
- $this->committerDate = $this->parseDate($this->committerDate);
+ list($this->committerName, $this->committerEmail, $committerDate) = $this->consumeNameEmailDate();
+ $this->committerDate = $this->parseDate($committerDate);
+
+ $this->consumeMergeTag();
// will consume an GPG signed commit if there is one
$this->consumeGPGSignature();
diff --git a/src/Gitonomy/Git/Parser/DiffParser.php b/src/Gitonomy/Git/Parser/DiffParser.php
index 2b84df8b..15e6c037 100644
--- a/src/Gitonomy/Git/Parser/DiffParser.php
+++ b/src/Gitonomy/Git/Parser/DiffParser.php
@@ -22,14 +22,31 @@ class DiffParser extends ParserBase
protected function doParse()
{
$this->files = [];
+ $indexes = [];
+ // Diff contains raw information
+ if (str_starts_with($this->content, ':')) {
+ while ($this->expects(':')) {
+ $this->consumeRegexp('/\d{6} \d{6} /');
+ $oldIndex = $this->consumeShortHash();
+ $this->consume(' ');
+ $newIndex = $this->consumeShortHash();
+ $this->consumeTo("\n");
+ $this->consumeNewLine();
+ $indexes[] = [$oldIndex, $newIndex];
+ }
+ $this->consumeNewLine();
+ } elseif (!$this->isFinished()) {
+ trigger_error('Using Diff::parse without raw information is deprecated. See https://github.com/gitonomy/gitlib/issues/227.', E_USER_DEPRECATED);
+ }
+ $fileIndex = 0;
while (!$this->isFinished()) {
// 1. title
- $vars = $this->consumeRegexp('/diff --git (a\/.*) (b\/.*)\n/');
+ $vars = $this->consumeRegexp("/diff --git \"?(a\/.*?)\"? \"?(b\/.*?)\"?\n/");
$oldName = $vars[1];
$newName = $vars[2];
- $oldIndex = null;
- $newIndex = null;
+ $oldIndex = isset($indexes[$fileIndex]) ? $indexes[$fileIndex][0] : null;
+ $newIndex = isset($indexes[$fileIndex]) ? $indexes[$fileIndex][1] : null;
$oldMode = null;
$newMode = null;
@@ -38,6 +55,7 @@ protected function doParse()
$newMode = $this->consumeTo("\n");
$this->consumeNewLine();
$oldMode = null;
+ $oldName = '/dev/null';
}
if ($this->expects('old mode ')) {
$oldMode = $this->consumeTo("\n");
@@ -49,6 +67,7 @@ protected function doParse()
if ($this->expects('deleted file mode ')) {
$oldMode = $this->consumeTo("\n");
$newMode = null;
+ $newName = '/dev/null';
$this->consumeNewLine();
}
@@ -74,14 +93,15 @@ protected function doParse()
}
$this->consumeNewLine();
+ //verifying if the file was deleted or created
if ($this->expects('--- ')) {
- $oldName = $this->consumeTo("\n");
+ $oldName = $this->consumeTo("\n") === '/dev/null' ? '/dev/null' : $oldName;
$this->consumeNewLine();
$this->consume('+++ ');
- $newName = $this->consumeTo("\n");
+ $newName = $this->consumeTo("\n") === '/dev/null' ? '/dev/null' : $newName;
$this->consumeNewLine();
} elseif ($this->expects('Binary files ')) {
- $vars = $this->consumeRegexp('/(.*) and (.*) differ\n/');
+ $vars = $this->consumeRegexp('/"?(.*?)"? and "?(.*?)"? differ\n/');
$isBinary = true;
$oldName = $vars[1];
$newName = $vars[2];
@@ -90,6 +110,9 @@ protected function doParse()
$oldName = $oldName === '/dev/null' ? null : substr($oldName, 2);
$newName = $newName === '/dev/null' ? null : substr($newName, 2);
+
+ $oldIndex = $oldIndex === null ? '' : $oldIndex;
+ $newIndex = $newIndex === null ? '' : $newIndex;
$oldIndex = preg_match('/^0+$/', $oldIndex) ? null : $oldIndex;
$newIndex = preg_match('/^0+$/', $newIndex) ? null : $newIndex;
$file = new File($oldName, $newName, $oldMode, $newMode, $oldIndex, $newIndex, $isBinary);
@@ -97,10 +120,10 @@ protected function doParse()
// 5. Diff
while ($this->expects('@@ ')) {
$vars = $this->consumeRegexp('/-(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))?/');
- $rangeOldStart = $vars[1];
- $rangeOldCount = $vars[2];
- $rangeNewStart = $vars[3];
- $rangeNewCount = isset($vars[4]) ? $vars[4] : $vars[2]; // @todo Ici, t'as pris un gros raccourci mon loulou
+ $rangeOldStart = (int) $vars[1];
+ $rangeOldCount = (int) ($vars[2] ?? 1);
+ $rangeNewStart = (int) $vars[3];
+ $rangeNewCount = (int) ($vars[4] ?? 1);
$this->consume(' @@');
$this->consumeTo("\n");
$this->consumeNewLine();
diff --git a/src/Gitonomy/Git/Parser/LogParser.php b/src/Gitonomy/Git/Parser/LogParser.php
index 2672186e..67b86687 100644
--- a/src/Gitonomy/Git/Parser/LogParser.php
+++ b/src/Gitonomy/Git/Parser/LogParser.php
@@ -37,19 +37,24 @@ protected function doParse()
}
$this->consume('author ');
- list($commit['authorName'], $commit['authorEmail'], $commit['authorDate']) = $this->consumeNameEmailDate();
- $commit['authorDate'] = $this->parseDate($commit['authorDate']);
+ list($commit['authorName'], $commit['authorEmail'], $authorDate) = $this->consumeNameEmailDate();
+ $commit['authorDate'] = $this->parseDate($authorDate);
$this->consumeNewLine();
$this->consume('committer ');
- list($commit['committerName'], $commit['committerEmail'], $commit['committerDate']) = $this->consumeNameEmailDate();
- $commit['committerDate'] = $this->parseDate($commit['committerDate']);
+ list($commit['committerName'], $commit['committerEmail'], $committerDate) = $this->consumeNameEmailDate();
+ $commit['committerDate'] = $this->parseDate($committerDate);
+
+ $this->consumeMergeTag();
// will consume an GPG signed commit if there is one
$this->consumeGPGSignature();
$this->consumeNewLine();
- $this->consumeNewLine();
+ $this->consumeUnsupportedLinesToNewLine();
+ if ($this->cursor < strlen($this->content)) {
+ $this->consumeNewLine();
+ }
$message = '';
if ($this->expects(' ')) {
@@ -72,4 +77,15 @@ protected function doParse()
$this->log[] = $commit;
}
}
+
+ protected function consumeUnsupportedLinesToNewLine()
+ {
+ // Consume any unsupported lines that may appear in the log output. For
+ // example, gitbutler headers or other custom metadata but this should
+ // work regardless of the content.
+ while (!$this->isFinished() && substr($this->content, $this->cursor, 1) !== "\n") {
+ $this->consumeTo("\n");
+ $this->consumeNewLine();
+ }
+ }
}
diff --git a/src/Gitonomy/Git/Parser/ParserBase.php b/src/Gitonomy/Git/Parser/ParserBase.php
index 94155ce1..45a80020 100644
--- a/src/Gitonomy/Git/Parser/ParserBase.php
+++ b/src/Gitonomy/Git/Parser/ParserBase.php
@@ -25,7 +25,7 @@ abstract protected function doParse();
public function parse($content)
{
$this->cursor = 0;
- $this->content = $content;
+ $this->content = $content ?? '';
$this->length = strlen($this->content);
$this->doParse();
@@ -59,7 +59,7 @@ protected function expects($expected)
protected function consumeShortHash()
{
- if (!preg_match('/([A-Za-z0-9]{7,40})/A', $this->content, $vars, null, $this->cursor)) {
+ if (!preg_match('/([A-Za-z0-9]{7,40})/A', $this->content, $vars, 0, $this->cursor)) {
throw new RuntimeException('No short hash found: '.substr($this->content, $this->cursor, 7));
}
@@ -70,7 +70,7 @@ protected function consumeShortHash()
protected function consumeHash()
{
- if (!preg_match('/([A-Za-z0-9]{40})/A', $this->content, $vars, null, $this->cursor)) {
+ if (!preg_match('/([A-Za-z0-9]{40})/A', $this->content, $vars, 0, $this->cursor)) {
throw new RuntimeException('No hash found: '.substr($this->content, $this->cursor, 40));
}
@@ -81,8 +81,8 @@ protected function consumeHash()
protected function consumeRegexp($regexp)
{
- if (!preg_match($regexp.'A', $this->content, $vars, null, $this->cursor)) {
- throw new RuntimeException('No match for regexp '.$regexp.' Upcoming: '.substr($this->content, $this->cursor, 30));
+ if (!preg_match($regexp.'A', $this->content, $vars, 0, $this->cursor)) {
+ throw new RuntimeException('No match for regexp '.$regexp.' Upcoming: '.substr($this->content, $this->cursor, 500));
}
$this->cursor += strlen($vars[0]);
@@ -136,4 +136,18 @@ protected function consumeGPGSignature()
return $this->consumeTo("\n\n");
}
+
+ protected function consumeMergeTag()
+ {
+ $expected = "\nmergetag ";
+ $length = strlen($expected);
+ $actual = substr($this->content, $this->cursor, $length);
+ if ($actual != $expected) {
+ return '';
+ }
+ $this->cursor += $length;
+
+ $this->consumeTo('-----END PGP SIGNATURE-----');
+ $this->consume('-----END PGP SIGNATURE-----');
+ }
}
diff --git a/src/Gitonomy/Git/Parser/TagParser.php b/src/Gitonomy/Git/Parser/TagParser.php
index 659be18a..1a1c084c 100644
--- a/src/Gitonomy/Git/Parser/TagParser.php
+++ b/src/Gitonomy/Git/Parser/TagParser.php
@@ -40,8 +40,8 @@ protected function doParse()
$this->consumeNewLine();
$this->consume('tagger ');
- list($this->taggerName, $this->taggerEmail, $this->taggerDate) = $this->consumeNameEmailDate();
- $this->taggerDate = $this->parseDate($this->taggerDate);
+ list($this->taggerName, $this->taggerEmail, $taggerDate) = $this->consumeNameEmailDate();
+ $this->taggerDate = $this->parseDate($taggerDate);
$this->consumeNewLine();
$this->consumeNewLine();
diff --git a/src/Gitonomy/Git/PushReference.php b/src/Gitonomy/Git/PushReference.php
index 3911bd2e..dea10823 100644
--- a/src/Gitonomy/Git/PushReference.php
+++ b/src/Gitonomy/Git/PushReference.php
@@ -24,6 +24,10 @@ class PushReference
{
const ZERO = '0000000000000000000000000000000000000000';
+ /**
+ * @var Repository
+ */
+ protected $repository;
/**
* @var string
*/
@@ -86,7 +90,7 @@ public function getAfter()
}
/**
- * @return array
+ * @return Log
*/
public function getLog($excludes = [])
{
@@ -98,6 +102,9 @@ public function getLog($excludes = [])
));
}
+ /**
+ * @return string
+ */
public function getRevision()
{
if ($this->isDelete()) {
diff --git a/src/Gitonomy/Git/Reference.php b/src/Gitonomy/Git/Reference.php
index 23f1cb75..96b8fa12 100644
--- a/src/Gitonomy/Git/Reference.php
+++ b/src/Gitonomy/Git/Reference.php
@@ -12,6 +12,9 @@
namespace Gitonomy\Git;
+use Gitonomy\Git\Exception\ProcessException;
+use Gitonomy\Git\Exception\ReferenceNotFoundException;
+
/**
* Reference in a Git repository.
*
@@ -29,16 +32,25 @@ public function __construct(Repository $repository, $revision, $commitHash = nul
$this->commitHash = $commitHash;
}
+ /**
+ * @return string
+ */
public function getFullname()
{
return $this->revision;
}
+ /**
+ * @return void
+ */
public function delete()
{
$this->repository->getReferences()->delete($this->getFullname());
}
+ /**
+ * @return string
+ */
public function getCommitHash()
{
if (null !== $this->commitHash) {
@@ -55,15 +67,16 @@ public function getCommitHash()
}
/**
- * Returns the commit associated to the reference.
- *
- * @return Commit
+ * @return Commit Commit associated to the reference.
*/
public function getCommit()
{
return $this->repository->getCommit($this->getCommitHash());
}
+ /**
+ * @return Commit
+ */
public function getLastModification($path = null)
{
return $this->getCommit()->getLastModification($path);
diff --git a/src/Gitonomy/Git/Reference/Branch.php b/src/Gitonomy/Git/Reference/Branch.php
index 0f263a76..0d27fb88 100644
--- a/src/Gitonomy/Git/Reference/Branch.php
+++ b/src/Gitonomy/Git/Reference/Branch.php
@@ -12,8 +12,10 @@
namespace Gitonomy\Git\Reference;
+use Gitonomy\Git\Exception\ProcessException;
use Gitonomy\Git\Exception\RuntimeException;
use Gitonomy\Git\Reference;
+use Gitonomy\Git\Util\StringHelper;
/**
* Representation of a branch reference.
@@ -53,6 +55,49 @@ public function isLocal()
return $this->local;
}
+ /**
+ * Check if this branch is merged to a destination branch
+ * Optionally, check only with remote branches.
+ *
+ * @param string $destinationBranchName
+ * @param bool $compareOnlyWithRemote
+ *
+ * @return null|bool
+ */
+ public function isMergedTo($destinationBranchName = 'master', $compareOnlyWithRemote = false)
+ {
+ $arguments = ['-a'];
+
+ if ($compareOnlyWithRemote) {
+ $arguments = ['-r'];
+ }
+
+ $arguments[] = '--merged';
+ $arguments[] = $destinationBranchName;
+
+ try {
+ $result = $this->repository->run('branch', $arguments);
+ } catch (ProcessException $e) {
+ throw new RuntimeException(
+ sprintf('Cannot determine if merged to the branch "%s"', $destinationBranchName),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ if (!$result) {
+ return false;
+ }
+
+ $output = explode("\n", trim(str_replace(['*', 'remotes/'], '', $result)));
+ $filtered_output = array_filter($output, static function ($v) {
+ return false === StringHelper::strpos($v, '->');
+ });
+ $trimmed_output = array_map('trim', $filtered_output);
+
+ return in_array($this->getName(), $trimmed_output, true);
+ }
+
private function detectBranchType()
{
if (null === $this->local) {
diff --git a/src/Gitonomy/Git/Reference/Tag.php b/src/Gitonomy/Git/Reference/Tag.php
index e80fde00..78c43b25 100644
--- a/src/Gitonomy/Git/Reference/Tag.php
+++ b/src/Gitonomy/Git/Reference/Tag.php
@@ -12,8 +12,10 @@
namespace Gitonomy\Git\Reference;
+use Gitonomy\Git\Commit;
use Gitonomy\Git\Exception\ProcessException;
use Gitonomy\Git\Exception\RuntimeException;
+use Gitonomy\Git\Parser\ReferenceParser;
use Gitonomy\Git\Parser\TagParser;
use Gitonomy\Git\Reference;
@@ -21,9 +23,12 @@
* Representation of a tag reference.
*
* @author Alexandre Salomé
+ * @author Bruce Wells
*/
class Tag extends Reference
{
+ protected $data;
+
public function getName()
{
if (!preg_match('#^refs/tags/(.*)$#', $this->revision, $vars)) {
@@ -49,6 +54,32 @@ public function isAnnotated()
return true;
}
+ /**
+ * Returns the actual commit associated with the tag, and not the hash of the tag if annotated.
+ *
+ * @return Commit
+ */
+ public function getCommit()
+ {
+ if ($this->isAnnotated()) {
+ try {
+ $output = $this->repository->run('show-ref', ['-d', '--tag', $this->revision]);
+ $parser = new ReferenceParser();
+ $parser->parse($output);
+
+ foreach ($parser->references as list($row)) {
+ $commitHash = $row;
+ }
+
+ return $this->repository->getCommit($commitHash);
+ } catch (ProcessException $e) {
+ // ignore the exception
+ }
+ }
+
+ return parent::getCommit();
+ }
+
/**
* Returns the tagger name.
*
@@ -72,7 +103,7 @@ public function getTaggerEmail()
/**
* Returns the authoring date.
*
- * @return DateTime A time object
+ * @return \DateTime A time object
*/
public function getTaggerDate()
{
@@ -160,9 +191,9 @@ private function getData($name)
array_shift($lines);
array_pop($lines);
- $data['bodyMessage'] = implode("\n", $lines);
+ $this->data['bodyMessage'] = implode("\n", $lines);
- return $data['bodyMessage'];
+ return $this->data['bodyMessage'];
}
$parser = new TagParser();
diff --git a/src/Gitonomy/Git/ReferenceBag.php b/src/Gitonomy/Git/ReferenceBag.php
index 178fd9f2..71ef9ae4 100644
--- a/src/Gitonomy/Git/ReferenceBag.php
+++ b/src/Gitonomy/Git/ReferenceBag.php
@@ -29,7 +29,7 @@ class ReferenceBag implements \Countable, \IteratorAggregate
/**
* Repository object.
*
- * @var Gitonomy\Git\Repository
+ * @var Repository
*/
protected $repository;
@@ -43,14 +43,14 @@ class ReferenceBag implements \Countable, \IteratorAggregate
/**
* List with all tags.
*
- * @var array
+ * @var Tag[]
*/
protected $tags;
/**
* List with all branches.
*
- * @var array
+ * @var Branch[]
*/
protected $branches;
@@ -64,7 +64,7 @@ class ReferenceBag implements \Countable, \IteratorAggregate
/**
* Constructor.
*
- * @param Gitonomy\Git\Repository $repository The repository
+ * @param Repository $repository The repository
*/
public function __construct($repository)
{
@@ -92,6 +92,9 @@ public function get($fullname)
return $this->references[$fullname];
}
+ /**
+ * @return bool
+ */
public function has($fullname)
{
$this->initialize();
@@ -99,6 +102,9 @@ public function has($fullname)
return isset($this->references[$fullname]);
}
+ /**
+ * @return Reference
+ */
public function update(Reference $reference)
{
$fullname = $reference->getFullname();
@@ -111,6 +117,9 @@ public function update(Reference $reference)
return $reference;
}
+ /**
+ * @return Reference
+ */
public function createBranch($name, $commitHash)
{
$branch = new Branch($this->repository, 'refs/heads/'.$name, $commitHash);
@@ -118,6 +127,9 @@ public function createBranch($name, $commitHash)
return $this->update($branch);
}
+ /**
+ * @return Reference
+ */
public function createTag($name, $commitHash)
{
$tag = new Tag($this->repository, 'refs/tags/'.$name, $commitHash);
@@ -125,6 +137,9 @@ public function createTag($name, $commitHash)
return $this->update($tag);
}
+ /**
+ * @return void
+ */
public function delete($fullname)
{
$this->repository->run('update-ref', ['-d', $fullname]);
@@ -132,6 +147,9 @@ public function delete($fullname)
unset($this->references[$fullname]);
}
+ /**
+ * @return bool
+ */
public function hasBranches()
{
$this->initialize();
@@ -154,6 +172,9 @@ public function hasTag($name)
return $this->has('refs/tags/'.$name);
}
+ /**
+ * @return Branch
+ */
public function getFirstBranch()
{
$this->initialize();
@@ -163,7 +184,7 @@ public function getFirstBranch()
}
/**
- * @return array An array of Tag objects
+ * @return Tag[] An array of Tag objects
*/
public function resolveTags($hash)
{
@@ -184,7 +205,7 @@ public function resolveTags($hash)
}
/**
- * @return array An array of Branch objects
+ * @return Branch[] An array of Branch objects
*/
public function resolveBranches($hash)
{
@@ -205,7 +226,7 @@ public function resolveBranches($hash)
}
/**
- * @return array An array of references
+ * @return Reference[] An array of references
*/
public function resolve($hash)
{
@@ -226,9 +247,7 @@ public function resolve($hash)
}
/**
- * Returns all tags.
- *
- * @return array
+ * @return Tag[] All tags.
*/
public function getTags()
{
@@ -238,9 +257,7 @@ public function getTags()
}
/**
- * Returns all branches.
- *
- * @return array
+ * @return Branch[] All branches.
*/
public function getBranches()
{
@@ -257,9 +274,7 @@ public function getBranches()
}
/**
- * Returns all locales branches.
- *
- * @return array
+ * @return Branch[] All local branches.
*/
public function getLocalBranches()
{
@@ -274,9 +289,7 @@ public function getLocalBranches()
}
/**
- * Returns all remote branches.
- *
- * @return array
+ * @return Branch[] All remote branches.
*/
public function getRemoteBranches()
{
@@ -341,11 +354,7 @@ protected function initialize()
$parser = new Parser\ReferenceParser();
$output = $this->repository->run('show-ref');
} catch (RuntimeException $e) {
- $output = $e->getOutput();
- $error = $e->getErrorOutput();
- if ($error) {
- throw new RuntimeException('Error while getting list of references: '.$error);
- }
+ return;
}
$parser->parse($output);
@@ -366,12 +375,6 @@ protected function initialize()
} elseif ($fullname === 'refs/stash') {
$reference = new Stash($this->repository, $fullname, $commitHash);
$this->references[$fullname] = $reference;
- } elseif (preg_match('#^refs/pull/(.*)$#', $fullname)) {
- // Do nothing here
- } elseif ($fullname === 'refs/notes/gtm-data') {
- // Do nothing here
- } else {
- throw new RuntimeException(sprintf('Unable to parse "%s"', $fullname));
}
}
}
@@ -381,6 +384,7 @@ protected function initialize()
*
* @see Countable
*/
+ #[\ReturnTypeWillChange]
public function count()
{
$this->initialize();
@@ -391,6 +395,7 @@ public function count()
/**
* @see IteratorAggregate
*/
+ #[\ReturnTypeWillChange]
public function getIterator()
{
$this->initialize();
diff --git a/src/Gitonomy/Git/Repository.php b/src/Gitonomy/Git/Repository.php
index 36e35850..296496ae 100644
--- a/src/Gitonomy/Git/Repository.php
+++ b/src/Gitonomy/Git/Repository.php
@@ -86,6 +86,11 @@ class Repository
*/
protected $environmentVariables;
+ /**
+ * @var bool
+ */
+ protected $inheritEnvironmentVariables;
+
/**
* Timeout that should be set for every running process.
*
@@ -113,14 +118,14 @@ class Repository
*/
public function __construct($dir, $options = [])
{
- $is_windows = defined('PHP_WINDOWS_VERSION_BUILD');
$options = array_merge([
- 'working_dir' => null,
- 'debug' => true,
- 'logger' => null,
- 'environment_variables' => $is_windows ? ['PATH' => getenv('path')] : [],
- 'command' => 'git',
- 'process_timeout' => 3600,
+ 'working_dir' => null,
+ 'debug' => true,
+ 'logger' => null,
+ 'command' => 'git',
+ 'environment_variables' => [],
+ 'inherit_environment_variables' => false,
+ 'process_timeout' => 3600,
], $options);
if (null !== $options['logger'] && !$options['logger'] instanceof LoggerInterface) {
@@ -131,10 +136,16 @@ public function __construct($dir, $options = [])
$this->initDir($dir, $options['working_dir']);
$this->objects = [];
+ $this->command = $options['command'];
$this->debug = (bool) $options['debug'];
- $this->environmentVariables = $options['environment_variables'];
$this->processTimeout = $options['process_timeout'];
- $this->command = $options['command'];
+
+ if (defined('PHP_WINDOWS_VERSION_BUILD') && isset($_SERVER['PATH']) && !isset($options['environment_variables']['PATH'])) {
+ $options['environment_variables']['PATH'] = $_SERVER['PATH'];
+ }
+
+ $this->environmentVariables = $options['environment_variables'];
+ $this->inheritEnvironmentVariables = $options['inherit_environment_variables'];
if (true === $this->debug && null !== $this->logger) {
$this->logger->debug(sprintf('Repository created (git dir: "%s", working dir: "%s")', $this->gitDir, $this->workingDir ?: 'none'));
@@ -304,11 +315,13 @@ public function getWorkingCopy()
/**
* Returns the reference list associated to the repository.
*
+ * @param bool $reload Reload references from the filesystem
+ *
* @return ReferenceBag
*/
- public function getReferences()
+ public function getReferences($reload = false)
{
- if (null === $this->referenceBag) {
+ if (null === $this->referenceBag || $reload) {
$this->referenceBag = new ReferenceBag($this);
}
@@ -363,6 +376,9 @@ public function getBlob($hash)
return $this->objects[$hash];
}
+ /**
+ * @return Blame
+ */
public function getBlame($revision, $file, $lineRange = null)
{
if (is_string($revision)) {
@@ -400,7 +416,7 @@ public function getDiff($revisions)
$revisions = new RevisionList($this, $revisions);
}
- $args = array_merge(['-r', '-p', '-m', '-M', '--no-commit-id', '--full-index'], $revisions->getAsTextArray());
+ $args = array_merge(['-r', '-p', '--raw', '-m', '-M', '--no-commit-id', '--full-index'], $revisions->getAsTextArray());
$diff = Diff::parse($this->run('diff', $args));
$diff->setRepository($this);
@@ -411,30 +427,19 @@ public function getDiff($revisions)
/**
* Returns the size of repository, in kilobytes.
*
- * @throws RuntimeException An error occurred while computing size
- *
* @return int A sum, in kilobytes
*/
public function getSize()
{
- $process = new Process(['du', '-skc', $this->gitDir]);
- $process->run();
-
- if (!preg_match('/(\d+)\s+total$/', trim($process->getOutput()), $vars)) {
- $message = sprintf("Unable to parse process output\ncommand: %s\noutput: %s", $process->getCommandLine(), $process->getOutput());
-
- if (null !== $this->logger) {
- $this->logger->error($message);
- }
-
- if (true === $this->debug) {
- throw new RuntimeException('unable to parse repository size output');
+ $totalBytes = 0;
+ $path = realpath($this->gitDir);
+ if ($path && file_exists($path)) {
+ foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS)) as $object) {
+ $totalBytes += $object->getSize();
}
-
- return;
}
- return $vars[1];
+ return (int) ($totalBytes / 1000 + 0.5);
}
/**
@@ -620,7 +625,13 @@ private function getProcess($command, $args = [])
$base[] = $command;
$process = new Process(array_merge($base, $args));
- $process->setEnv($this->environmentVariables);
+
+ if ($this->inheritEnvironmentVariables) {
+ $process->setEnv(array_replace($_SERVER, $this->environmentVariables));
+ } else {
+ $process->setEnv($this->environmentVariables);
+ }
+
$process->setTimeout($this->processTimeout);
$process->setIdleTimeout($this->processTimeout);
diff --git a/src/Gitonomy/Git/RevisionList.php b/src/Gitonomy/Git/RevisionList.php
index 760abb67..1c1a1dcd 100644
--- a/src/Gitonomy/Git/RevisionList.php
+++ b/src/Gitonomy/Git/RevisionList.php
@@ -49,16 +49,21 @@ public function __construct(Repository $repository, $revisions)
$this->revisions = $revisions;
}
+ /**
+ * @return Revision[]
+ */
public function getAll()
{
return $this->revisions;
}
+ #[\ReturnTypeWillChange]
public function getIterator()
{
return new \ArrayIterator($this->revisions);
}
+ #[\ReturnTypeWillChange]
public function count()
{
return count($this->revisions);
diff --git a/src/Gitonomy/Git/Tree.php b/src/Gitonomy/Git/Tree.php
index d3179837..7830cfa4 100644
--- a/src/Gitonomy/Git/Tree.php
+++ b/src/Gitonomy/Git/Tree.php
@@ -24,6 +24,7 @@ class Tree
protected $hash;
protected $isInitialized = false;
protected $entries;
+ protected $entriesByType;
public function __construct(Repository $repository, $hash)
{
@@ -47,31 +48,68 @@ protected function initialize()
$parser->parse($output);
$this->entries = [];
+ $this->entriesByType = [
+ 'blob' => [],
+ 'tree' => [],
+ 'commit' => [],
+ ];
foreach ($parser->entries as $entry) {
list($mode, $type, $hash, $name) = $entry;
if ($type == 'blob') {
- $this->entries[$name] = [$mode, $this->repository->getBlob($hash)];
+ $treeEntry = [$mode, $this->repository->getBlob($hash)];
} elseif ($type == 'tree') {
- $this->entries[$name] = [$mode, $this->repository->getTree($hash)];
+ $treeEntry = [$mode, $this->repository->getTree($hash)];
} else {
- $this->entries[$name] = [$mode, new CommitReference($hash)];
+ $treeEntry = [$mode, new CommitReference($hash)];
}
+ $this->entries[$name] = $treeEntry;
+ $this->entriesByType[$type][$name] = $treeEntry;
}
$this->isInitialized = true;
}
/**
- * @return array An associative array name => $object
+ * @return array An associative array name => $object
*/
- public function getEntries()
+ public function getEntries(): array
{
$this->initialize();
return $this->entries;
}
+ /**
+ * @return array An associative array of name => [mode, commit reference]
+ */
+ public function getCommitReferenceEntries(): array
+ {
+ $this->initialize();
+
+ return $this->entriesByType['commit'];
+ }
+
+ /**
+ * @return array An associative array of name => [mode, tree]
+ */
+ public function getTreeEntries(): array
+ {
+ $this->initialize();
+
+ return $this->entriesByType['tree'];
+ }
+
+ /**
+ * @return array An associative array of name => [mode, blob]
+ */
+ public function getBlobEntries(): array
+ {
+ $this->initialize();
+
+ return $this->entriesByType['blob'];
+ }
+
public function getEntry($name)
{
$this->initialize();
@@ -96,7 +134,7 @@ public function resolvePath($path)
foreach ($segments as $segment) {
if ($element instanceof self) {
$element = $element->getEntry($segment);
- } elseif ($entry instanceof Blob) {
+ } elseif ($element instanceof Blob) {
throw new InvalidArgumentException('Unresolvable path');
} else {
throw new UnexpectedValueException('Unknow type of element');
diff --git a/src/Gitonomy/Git/WorkingCopy.php b/src/Gitonomy/Git/WorkingCopy.php
index 057253b5..a94fbbb9 100644
--- a/src/Gitonomy/Git/WorkingCopy.php
+++ b/src/Gitonomy/Git/WorkingCopy.php
@@ -35,11 +35,6 @@ public function __construct(Repository $repository)
}
}
- public function getStatus()
- {
- return WorkingStatus::parseOutput();
- }
-
public function getUntrackedFiles()
{
$lines = explode("\0", $this->run('status', ['--porcelain', '--untracked-files=all', '-z']));
@@ -55,7 +50,7 @@ public function getUntrackedFiles()
public function getDiffPending()
{
- $diff = Diff::parse($this->run('diff', ['-r', '-p', '-m', '-M', '--full-index']));
+ $diff = Diff::parse($this->run('diff', ['-r', '-p', '--raw', '-m', '-M', '--full-index']));
$diff->setRepository($this->repository);
return $diff;
@@ -63,7 +58,7 @@ public function getDiffPending()
public function getDiffStaged()
{
- $diff = Diff::parse($this->run('diff', ['-r', '-p', '-m', '-M', '--full-index', '--staged']));
+ $diff = Diff::parse($this->run('diff', ['-r', '-p', '--raw', '-m', '-M', '--full-index', '--staged']));
$diff->setRepository($this->repository);
return $diff;
diff --git a/tests/Gitonomy/Git/Tests/AbstractTest.php b/tests/Gitonomy/Git/Tests/AbstractTest.php
index a47f3792..0b9a51b6 100644
--- a/tests/Gitonomy/Git/Tests/AbstractTest.php
+++ b/tests/Gitonomy/Git/Tests/AbstractTest.php
@@ -18,8 +18,9 @@
abstract class AbstractTest extends TestCase
{
- const REPOSITORY_URL = 'http://github.com/gitonomy/foobar.git';
+ const REPOSITORY_URL = 'https://github.com/gitonomy/foobar.git';
+ const NO_MESSAGE_COMMIT = '011cd0c1625190d2959ee9a8f9f822006d94b661';
const LONGFILE_COMMIT = '4f17752acc9b7c54ba679291bf24cb7d354f0f4f';
const BEFORE_LONGFILE_COMMIT = 'e0ec50e2af75fa35485513f60b2e658e245227e9';
const LONGMESSAGE_COMMIT = '3febd664b6886344a9b32d70657687ea4b1b4fab';
@@ -117,7 +118,7 @@ public static function createTempDir()
*
* @param string $dir directory to delete
*/
- public static function deleteDir($dir)
+ protected static function deleteDir($dir)
{
$iterator = new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS);
$iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
diff --git a/tests/Gitonomy/Git/Tests/AdminTest.php b/tests/Gitonomy/Git/Tests/AdminTest.php
index d000dd44..0f7460fa 100644
--- a/tests/Gitonomy/Git/Tests/AdminTest.php
+++ b/tests/Gitonomy/Git/Tests/AdminTest.php
@@ -13,6 +13,7 @@
namespace Gitonomy\Git\Tests;
use Gitonomy\Git\Admin;
+use Gitonomy\Git\Exception\RuntimeException;
use Gitonomy\Git\Reference\Branch;
use Gitonomy\Git\Repository;
@@ -20,14 +21,20 @@ class AdminTest extends AbstractTest
{
private $tmpDir;
- public function setUp()
+ /**
+ * @before
+ */
+ public function setUpTmpDir()
{
$this->tmpDir = self::createTempDir();
}
- public function tearDown()
+ /**
+ * @after
+ */
+ public function tearDownTmpDir()
{
- $this->deleteDir(self::createTempDir());
+ self::deleteDir(self::createTempDir());
}
public function testBare()
@@ -37,8 +44,8 @@ public function testBare()
$objectDir = $this->tmpDir.'/objects';
$this->assertTrue($repository->isBare(), 'Repository is bare');
- $this->assertTrue(is_dir($objectDir), 'objects/ folder is present');
- $this->assertTrue($repository instanceof Repository, 'Admin::init returns a repository');
+ $this->assertDirectoryExists($objectDir, 'objects/ folder is present');
+ $this->assertInstanceOf(Repository::class, $repository, 'Admin::init returns a repository');
$this->assertEquals($this->tmpDir, $repository->getGitDir(), 'The folder passed as argument is git dir');
$this->assertNull($repository->getWorkingDir(), 'No working dir in bare repository');
}
@@ -50,8 +57,8 @@ public function testNotBare()
$objectDir = $this->tmpDir.'/.git/objects';
$this->assertFalse($repository->isBare(), 'Repository is not bare');
- $this->assertTrue(is_dir($objectDir), 'objects/ folder is present');
- $this->assertTrue($repository instanceof Repository, 'Admin::init returns a repository');
+ $this->assertDirectoryExists($objectDir, 'objects/ folder is present');
+ $this->assertInstanceOf(Repository::class, $repository, 'Admin::init returns a repository');
$this->assertEquals($this->tmpDir.'/.git', $repository->getGitDir(), 'git dir as subfolder of argument');
$this->assertEquals($this->tmpDir, $repository->getWorkingDir(), 'working dir present in bare repository');
}
@@ -67,12 +74,12 @@ public function testClone($repository)
$newRefs = array_keys($new->getReferences()->getAll());
- $this->assertTrue(in_array('refs/heads/master', $newRefs));
- $this->assertTrue(in_array('refs/tags/0.1', $newRefs));
+ $this->assertContains('refs/heads/master', $newRefs);
+ $this->assertContains('refs/tags/0.1', $newRefs);
if ($repository->isBare()) {
$this->assertEquals($newDir, $new->getGitDir());
- $this->assertTrue(in_array('refs/heads/new-feature', $newRefs));
+ $this->assertContains('refs/heads/new-feature', $newRefs);
} else {
$this->assertEquals($newDir.'/.git', $new->getGitDir());
$this->assertEquals($newDir, $new->getWorkingDir());
@@ -90,7 +97,7 @@ public function testCloneBranchBare()
self::registerDeletion($new);
$head = $new->getHead();
- $this->assertTrue($head instanceof Branch, 'HEAD is a branch');
+ $this->assertInstanceOf(Branch::class, $head, 'HEAD is a branch');
$this->assertEquals('new-feature', $head->getName(), 'HEAD is branch new-feature');
}
@@ -105,7 +112,7 @@ public function testCloneBranchNotBare()
self::registerDeletion($new);
$head = $new->getHead();
- $this->assertTrue($head instanceof Branch, 'HEAD is a branch');
+ $this->assertInstanceOf(Branch::class, $head, 'HEAD is a branch');
$this->assertEquals('new-feature', $head->getName(), 'HEAD is branch new-feature');
}
@@ -120,14 +127,14 @@ public function testMirror($repository)
$newRefs = array_keys($new->getReferences()->getAll());
- $this->assertTrue(in_array('refs/heads/master', $newRefs));
- $this->assertTrue(in_array('refs/tags/0.1', $newRefs));
+ $this->assertContains('refs/heads/master', $newRefs);
+ $this->assertContains('refs/tags/0.1', $newRefs);
$this->assertEquals($newDir, $new->getGitDir());
if ($repository->isBare()) {
- $this->assertTrue(in_array('refs/heads/new-feature', $newRefs));
+ $this->assertContains('refs/heads/new-feature', $newRefs);
} else {
- $this->assertTrue(in_array('refs/remotes/origin/new-feature', $newRefs));
+ $this->assertContains('refs/remotes/origin/new-feature', $newRefs);
}
}
@@ -149,10 +156,27 @@ public function testCheckInvalidRepository()
}
/**
- * @expectedException RuntimeException
+ * @dataProvider provideFoobar
*/
+ public function testCheckValidRepositoryAndBranch($repository)
+ {
+ $url = $repository->getGitDir();
+ $this->assertTrue(Admin::isValidRepositoryAndBranch($url, 'master'));
+ }
+
+ /**
+ * @dataProvider provideFoobar
+ */
+ public function testCheckInvalidRepositoryAndBranch($repository)
+ {
+ $url = $repository->getGitDir();
+ $this->assertFalse(Admin::isValidRepositoryAndBranch($url, 'invalid-branch-name'));
+ }
+
public function testExistingFile()
{
+ $this->expectException(RuntimeException::class);
+
$file = $this->tmpDir.'/test';
touch($file);
@@ -169,8 +193,8 @@ public function testCloneRepository()
$newRefs = array_keys($new->getReferences()->getAll());
- $this->assertTrue(in_array('refs/heads/master', $newRefs));
- $this->assertTrue(in_array('refs/tags/0.1', $newRefs));
+ $this->assertContains('refs/heads/master', $newRefs);
+ $this->assertContains('refs/tags/0.1', $newRefs);
$this->assertEquals($newDir.'/.git', $new->getGitDir());
$this->assertEquals($newDir, $new->getWorkingDir());
diff --git a/tests/Gitonomy/Git/Tests/BlobTest.php b/tests/Gitonomy/Git/Tests/BlobTest.php
index 8dbe2719..431a0a46 100644
--- a/tests/Gitonomy/Git/Tests/BlobTest.php
+++ b/tests/Gitonomy/Git/Tests/BlobTest.php
@@ -12,6 +12,8 @@
namespace Gitonomy\Git\Tests;
+use Gitonomy\Git\Exception\RuntimeException;
+
class BlobTest extends AbstractTest
{
const README_FRAGMENT = 'Foo Bar project';
@@ -21,6 +23,11 @@ public function getReadmeBlob($repository)
return $repository->getCommit(self::LONGFILE_COMMIT)->getTree()->resolvePath('README.md');
}
+ public function getImageBlob($repository)
+ {
+ return $repository->getCommit(self::LONGFILE_COMMIT)->getTree()->resolvePath('image.jpg');
+ }
+
/**
* @dataProvider provideFoobar
*/
@@ -28,15 +35,20 @@ public function testGetContent($repository)
{
$blob = $this->getReadmeBlob($repository);
- $this->assertContains(self::README_FRAGMENT, $blob->getContent());
+ if (method_exists($this, 'assertStringContainsString')) {
+ $this->assertStringContainsString(self::README_FRAGMENT, $blob->getContent());
+ } else {
+ $this->assertContains(self::README_FRAGMENT, $blob->getContent());
+ }
}
/**
* @dataProvider provideFoobar
- * @expectedException RuntimeException
*/
public function testNotExisting($repository)
{
+ $this->expectException(RuntimeException::class);
+
$blob = $repository->getBlob('foobar');
$blob->getContent();
}
@@ -47,7 +59,12 @@ public function testNotExisting($repository)
public function testGetMimetype($repository)
{
$blob = $this->getReadmeBlob($repository);
- $this->assertRegexp('#text/plain#', $blob->getMimetype());
+
+ if (method_exists($this, 'assertMatchesRegularExpression')) {
+ $this->assertMatchesRegularExpression('#text/plain#', $blob->getMimetype());
+ } else {
+ $this->assertRegExp('#text/plain#', $blob->getMimetype());
+ }
}
/**
@@ -55,8 +72,10 @@ public function testGetMimetype($repository)
*/
public function testIsText($repository)
{
- $blob = $this->getReadmeBlob($repository);
- $this->assertTrue($blob->isText());
+ $readmeBlob = $this->getReadmeBlob($repository);
+ $this->assertTrue($readmeBlob->isText());
+ $imageBlob = $this->getImageBlob($repository);
+ $this->assertFalse($imageBlob->isText());
}
/**
@@ -64,7 +83,9 @@ public function testIsText($repository)
*/
public function testIsBinary($repository)
{
- $blob = $this->getReadmeBlob($repository);
- $this->assertFalse($blob->isBinary());
+ $readmeBlob = $this->getReadmeBlob($repository);
+ $this->assertFalse($readmeBlob->isBinary());
+ $imageBlob = $this->getImageBlob($repository);
+ $this->assertTrue($imageBlob->isBinary());
}
}
diff --git a/tests/Gitonomy/Git/Tests/CommitTest.php b/tests/Gitonomy/Git/Tests/CommitTest.php
index d33b478e..7d094648 100644
--- a/tests/Gitonomy/Git/Tests/CommitTest.php
+++ b/tests/Gitonomy/Git/Tests/CommitTest.php
@@ -14,6 +14,10 @@
use Gitonomy\Git\Commit;
use Gitonomy\Git\Diff\Diff;
+use Gitonomy\Git\Exception\InvalidArgumentException;
+use Gitonomy\Git\Exception\ReferenceNotFoundException;
+use Gitonomy\Git\Repository;
+use Gitonomy\Git\Tree;
class CommitTest extends AbstractTest
{
@@ -26,7 +30,7 @@ public function testGetDiff($repository)
$diff = $commit->getDiff();
- $this->assertTrue($diff instanceof Diff, 'getDiff() returns a Diff object');
+ $this->assertInstanceOf(Diff::class, $diff, 'getDiff() returns a Diff object');
}
/**
@@ -41,13 +45,13 @@ public function testGetHash($repository)
/**
* @dataProvider provideFoobar
- *
- * @expectedException Gitonomy\Git\Exception\ReferenceNotFoundException
- * @expectedExceptionMessage Reference not found: "that-hash-doest-not-exists"
*/
public function testInvalideHashThrowException($repository)
{
- $commit = new Commit($repository, 'that-hash-doest-not-exists');
+ $this->expectException(ReferenceNotFoundException::class);
+ $this->expectExceptionMessage('Reference not found: "that-hash-doest-not-exists"');
+
+ new Commit($repository, 'that-hash-doest-not-exists');
}
/**
@@ -67,7 +71,7 @@ public function testGetParentHashes_WithNoParent($repository)
{
$commit = $repository->getCommit(self::INITIAL_COMMIT);
- $this->assertEquals(0, count($commit->getParentHashes()), 'No parent on initial commit');
+ $this->assertCount(0, $commit->getParentHashes(), 'No parent on initial commit');
}
/**
@@ -78,7 +82,7 @@ public function testGetParentHashes_WithOneParent($repository)
$commit = $repository->getCommit(self::LONGFILE_COMMIT);
$parents = $commit->getParentHashes();
- $this->assertEquals(1, count($parents), 'One parent found');
+ $this->assertCount(1, $parents, 'One parent found');
$this->assertEquals(self::BEFORE_LONGFILE_COMMIT, $parents[0], 'Parent hash is correct');
}
@@ -90,8 +94,8 @@ public function testGetParents_WithOneParent($repository)
$commit = $repository->getCommit(self::LONGFILE_COMMIT);
$parents = $commit->getParents();
- $this->assertEquals(1, count($parents), 'One parent found');
- $this->assertTrue($parents[0] instanceof Commit, 'First parent is a Commit object');
+ $this->assertCount(1, $parents, 'One parent found');
+ $this->assertInstanceOf(Commit::class, $parents[0], 'First parent is a Commit object');
$this->assertEquals(self::BEFORE_LONGFILE_COMMIT, $parents[0]->getHash(), "First parents's hash is correct");
}
@@ -112,7 +116,7 @@ public function testGetTree($repository)
{
$commit = $repository->getCommit(self::LONGFILE_COMMIT);
- $this->assertInstanceOf('Gitonomy\Git\Tree', $commit->getTree(), 'Tree is a tree');
+ $this->assertInstanceOf(Tree::class, $commit->getTree(), 'Tree is a tree');
$this->assertEquals('b06890c7b10904979d2f69613c2ccda30aafe262', $commit->getTree()->getHash(), 'Tree hash is correct');
}
@@ -186,6 +190,31 @@ public function testGetMessage($repository)
$this->assertEquals('add a long file'."\n", $commit->getMessage());
}
+ /**
+ * @dataProvider provideFoobar
+ *
+ * @param $repository Repository
+ */
+ public function testGetEmptyMessage($repository)
+ {
+ $commit = $repository->getCommit(self::NO_MESSAGE_COMMIT);
+
+ $this->assertEquals('', $commit->getMessage());
+ }
+
+ /**
+ * @dataProvider provideFoobar
+ *
+ * @param $repository Repository
+ */
+ public function testGetEmptyMessageFromLog($repository)
+ {
+ $commit = $repository->getCommit(self::NO_MESSAGE_COMMIT);
+ $commitMessageFromLog = $commit->getLog()->getCommits()[0]->getMessage();
+
+ $this->assertEquals('', $commitMessageFromLog);
+ }
+
/**
* This test ensures that GPG signed commits does not break the reading of a commit
* message.
@@ -224,14 +253,8 @@ public function testGetShortMessage($repository)
public function testGetBodyMessage($repository)
{
$commit = $repository->getCommit(self::LONGMESSAGE_COMMIT);
- $message = <<<'EOL'
-If you want to know everything,
-I ran something like `chmox +x test.sh`
-
-Hello and good bye.
-
-EOL;
-
+ $nl = chr(10);
+ $message = "If you want to know everything,{$nl}I ran something like `chmox +x test.sh`{$nl}{$nl}Hello and good bye.{$nl}";
$this->assertEquals($message, $commit->getBodyMessage());
$commit = $repository->getCommit(self::INITIAL_COMMIT);
@@ -240,11 +263,12 @@ public function testGetBodyMessage($repository)
}
/**
- * @expectedException InvalidArgumentException
* @dataProvider provideFoobar
*/
public function testGetIncludingBranchesException($repository)
{
+ $this->expectException(InvalidArgumentException::class);
+
$commit = $repository->getCommit(self::INITIAL_COMMIT);
$commit->getIncludingBranches(false, false);
diff --git a/tests/Gitonomy/Git/Tests/DiffTest.php b/tests/Gitonomy/Git/Tests/DiffTest.php
index d260c5ed..e51fc7fc 100644
--- a/tests/Gitonomy/Git/Tests/DiffTest.php
+++ b/tests/Gitonomy/Git/Tests/DiffTest.php
@@ -13,6 +13,8 @@
namespace Gitonomy\Git\Tests;
use Gitonomy\Git\Diff\Diff;
+use Gitonomy\Git\Diff\File;
+use Gitonomy\Git\Repository;
class DiffTest extends AbstractTest
{
@@ -20,6 +22,7 @@ class DiffTest extends AbstractTest
const CREATE_COMMIT = 'e6fa3c792facc06faa049a6938c84c411954deb5';
const RENAME_COMMIT = '6640e0ef31518054847a1876328e26ee64083e0a';
const CHANGEMODE_COMMIT = '93da965f58170f13017477b9a608657e87e23230';
+ const FILE_WITH_UMLAUTS_COMMIT = '8defb9217692dc1f4c18e05e343ca91cf5047702';
/**
* @dataProvider provideFoobar
@@ -45,7 +48,7 @@ protected function verifyCreateCommitDiff(Diff $diff)
{
$files = $diff->getFiles();
- $this->assertEquals(2, count($files), '1 file in diff');
+ $this->assertCount(2, $files, '1 file in diff');
$this->assertTrue($files[0]->isCreation(), 'script_A.php created');
@@ -65,7 +68,7 @@ public function testGetFiles_Modification($repository)
{
$files = $repository->getCommit(self::BEFORE_LONGFILE_COMMIT)->getDiff()->getFiles();
- $this->assertEquals(1, count($files), '1 files in diff');
+ $this->assertCount(1, $files, '1 files in diff');
$this->assertTrue($files[0]->isModification(), 'image.jpg modified');
@@ -86,7 +89,7 @@ public function testGetFiles_Deletion($repository)
{
$files = $repository->getCommit(self::DELETE_COMMIT)->getDiff()->getFiles();
- $this->assertEquals(1, count($files), '1 files modified');
+ $this->assertCount(1, $files, '1 files modified');
$this->assertTrue($files[0]->isDeletion(), 'File deletion');
$this->assertEquals('script_B.php', $files[0]->getOldName(), 'verify old filename');
@@ -100,7 +103,7 @@ public function testGetFiles_Rename($repository)
{
$files = $repository->getCommit(self::RENAME_COMMIT)->getDiff()->getFiles();
- $this->assertEquals(1, count($files), '1 files modified');
+ $this->assertCount(1, $files, '1 files modified');
$this->assertTrue($files[0]->isModification());
$this->assertTrue($files[0]->isRename());
@@ -116,7 +119,7 @@ public function testGetFiles_Changemode($repository)
{
$files = $repository->getCommit(self::CHANGEMODE_COMMIT)->getDiff()->getFiles();
- $this->assertEquals(1, count($files), '1 files modified');
+ $this->assertCount(1, $files, '1 files modified');
$this->assertTrue($files[0]->isModification());
$this->assertTrue($files[0]->isChangeMode());
@@ -134,10 +137,148 @@ public function testDiffRangeParse($repository)
$changes = $files[0]->getChanges();
- $this->assertEquals(0, $changes[0]->getRangeOldStart());
- $this->assertEquals(0, $changes[0]->getRangeOldCount());
+ $this->assertSame(0, $changes[0]->getRangeOldStart());
+ $this->assertSame(0, $changes[0]->getRangeOldCount());
- $this->assertEquals(1, $changes[0]->getRangeNewStart());
- $this->assertEquals(0, $changes[0]->getRangeNewCount());
+ $this->assertSame(1, $changes[0]->getRangeNewStart());
+ $this->assertSame(1, $changes[0]->getRangeNewCount());
+ }
+
+ /**
+ * @dataProvider provideFoobar
+ */
+ public function testWorksWithUmlauts($repository)
+ {
+ $files = $repository->getCommit(self::FILE_WITH_UMLAUTS_COMMIT)->getDiff()->getFiles();
+ $this->assertSame('file_with_umlauts_\303\244\303\266\303\274', $files[0]->getNewName());
+ }
+
+ public function testDeleteFileWithoutRaw()
+ {
+ $deprecationCalled = false;
+ $self = $this;
+ set_error_handler(function (int $errno, string $errstr) use ($self, &$deprecationCalled): void {
+ $deprecationCalled = true;
+ $self->assertSame('Using Diff::parse without raw information is deprecated. See https://github.com/gitonomy/gitlib/issues/227.', $errstr);
+ }, E_USER_DEPRECATED);
+
+ $diff = Diff::parse(<<<'DIFF'
+ diff --git a/test b/test
+ deleted file mode 100644
+ index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
+
+ DIFF);
+ $firstFile = $diff->getFiles()[0];
+
+ restore_exception_handler();
+
+ $this->assertTrue($deprecationCalled);
+ $this->assertFalse($firstFile->isCreation());
+ // TODO: Enable after #226 is merged
+ //$this->assertTrue($firstFile->isDeletion());
+ //$this->assertFalse($firstFile->isChangeMode());
+ $this->assertSame('e69de29bb2d1d6434b8b29ae775ad8c2e48c5391', $firstFile->getOldIndex());
+ $this->assertNull($firstFile->getNewIndex());
+ }
+
+ public function testModeChangeFileWithoutRaw()
+ {
+ $deprecationCalled = false;
+ $self = $this;
+ set_error_handler(function (int $errno, string $errstr) use ($self, &$deprecationCalled): void {
+ $deprecationCalled = true;
+ $self->assertSame('Using Diff::parse without raw information is deprecated. See https://github.com/gitonomy/gitlib/issues/227.', $errstr);
+ }, E_USER_DEPRECATED);
+
+ $diff = Diff::parse(<<<'DIFF'
+ diff --git a/a.out b/a.out
+ old mode 100755
+ new mode 100644
+
+ DIFF);
+ $firstFile = $diff->getFiles()[0];
+
+ restore_exception_handler();
+
+ $this->assertTrue($deprecationCalled);
+ $this->assertFalse($firstFile->isCreation());
+ $this->assertFalse($firstFile->isDeletion());
+ $this->assertTrue($firstFile->isChangeMode());
+ $this->assertSame('', $firstFile->getOldIndex());
+ $this->assertSame('', $firstFile->getNewIndex());
+ }
+
+ public function testModeChangeFileWithRaw()
+ {
+ $deprecationCalled = false;
+ set_error_handler(function (int $errno, string $errstr) use (&$deprecationCalled): void {
+ $deprecationCalled = true;
+ }, E_USER_DEPRECATED);
+
+ $diff = Diff::parse(<<<'DIFF'
+ :100755 100644 d1af4b23d0cc9313e5b2d3ef2fb9696c94afaa81 d1af4b23d0cc9313e5b2d3ef2fb9696c94afaa81 M a.out
+
+ diff --git a/a.out b/a.out
+ old mode 100755
+ new mode 100644
+
+ DIFF);
+ $firstFile = $diff->getFiles()[0];
+
+ restore_exception_handler();
+
+ $this->assertFalse($deprecationCalled);
+ $this->assertFalse($firstFile->isCreation());
+ $this->assertFalse($firstFile->isDeletion());
+ $this->assertTrue($firstFile->isChangeMode());
+ $this->assertSame('d1af4b23d0cc9313e5b2d3ef2fb9696c94afaa81', $firstFile->getOldIndex());
+ $this->assertSame('d1af4b23d0cc9313e5b2d3ef2fb9696c94afaa81', $firstFile->getNewIndex());
+ }
+
+ public function testThrowErrorOnBlobGetWithoutIndex()
+ {
+ $repository = $this->getMockBuilder(Repository::class)->disableOriginalConstructor()->getMock();
+ $file = new File('oldName', 'newName', '100755', '100644', '', '', false);
+ $file->setRepository($repository);
+
+ try {
+ $file->getOldBlob();
+ } catch(\RuntimeException $exception) {
+ $this->assertSame('Index is missing to return Blob object.', $exception->getMessage());
+ }
+
+ try {
+ $file->getNewBlob();
+ } catch(\RuntimeException $exception) {
+ $this->assertSame('Index is missing to return Blob object.', $exception->getMessage());
+ }
+
+ $this->assertFalse($file->isCreation());
+ $this->assertFalse($file->isDeletion());
+ $this->assertTrue($file->isChangeMode());
+ $this->assertSame('', $file->getOldIndex());
+ $this->assertSame('', $file->getNewIndex());
+ }
+
+ public function testEmptyNewFile()
+ {
+ $diff = Diff::parse("diff --git a/test b/test\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391\n");
+ $firstFile = $diff->getFiles()[0];
+
+ $this->assertTrue($firstFile->isCreation());
+ $this->assertFalse($firstFile->isDeletion());
+ $this->assertSame('test', $firstFile->getNewName());
+ $this->assertNull($firstFile->getOldName());
+ }
+
+ public function testEmptyOldFile()
+ {
+ $diff = Diff::parse("diff --git a/test b/test\ndeleted file mode 100644\nindex e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000\n");
+ $firstFile = $diff->getFiles()[0];
+
+ $this->assertFalse($firstFile->isCreation());
+ $this->assertTrue($firstFile->isDeletion());
+ $this->assertNull($firstFile->getNewName());
+ $this->assertSame('test', $firstFile->getOldName());
}
}
diff --git a/tests/Gitonomy/Git/Tests/HooksTest.php b/tests/Gitonomy/Git/Tests/HooksTest.php
index 86cb7606..1b09fe55 100644
--- a/tests/Gitonomy/Git/Tests/HooksTest.php
+++ b/tests/Gitonomy/Git/Tests/HooksTest.php
@@ -12,11 +12,17 @@
namespace Gitonomy\Git\Tests;
+use Gitonomy\Git\Exception\InvalidArgumentException;
+use Gitonomy\Git\Exception\LogicException;
+
class HooksTest extends AbstractTest
{
private static $symlinkOnWindows = null;
- public static function setUpBeforeClass()
+ /**
+ * @beforeClass
+ */
+ public static function setUpWindows()
{
if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
self::$symlinkOnWindows = true;
@@ -49,7 +55,8 @@ public function assertHasHook($repository, $hook)
$file = $this->hookPath($repository, $hook);
$this->assertTrue($repository->getHooks()->has($hook), "hook $hook in repository");
- $this->assertTrue(file_exists($file), "Hook $hook is present");
+
+ $this->assertFileExists($file, "Hook $hook is present");
}
public function assertNoHook($repository, $hook)
@@ -57,7 +64,12 @@ public function assertNoHook($repository, $hook)
$file = $this->hookPath($repository, $hook);
$this->assertFalse($repository->getHooks()->has($hook), "No hook $hook in repository");
- $this->assertFalse(file_exists($file), "Hook $hook is not present");
+
+ if (method_exists($this, 'assertFileDoesNotExist')) {
+ $this->assertFileDoesNotExist($file, "Hook $hook is not present");
+ } else {
+ $this->assertFileNotExists($file, "Hook $hook is not present");
+ }
}
/**
@@ -72,10 +84,11 @@ public function testHas($repository)
/**
* @dataProvider provideFoobar
- * @expectedException InvalidArgumentException
*/
public function testGet_InvalidName_ThrowsException($repository)
{
+ $this->expectException(InvalidArgumentException::class);
+
$repository->getHooks()->get('foo');
}
@@ -100,15 +113,21 @@ public function testSymlink($repository)
$repository->getHooks()->setSymlink('foo', $file);
$this->assertTrue(is_link($this->hookPath($repository, 'foo')), 'foo hook is a symlink');
- $this->assertEquals($file, readlink($this->hookPath($repository, 'foo')), 'target of symlink is correct');
+
+ $this->assertEquals(
+ str_replace('\\', '/', $file),
+ str_replace('\\', '/', readlink($this->hookPath($repository, 'foo'))),
+ 'target of symlink is correct'
+ );
}
/**
* @dataProvider provideFoobar
- * @expectedException LogicException
*/
public function testSymlink_WithExisting_ThrowsLogicException($repository)
{
+ $this->expectException(LogicException::class);
+
$this->markAsSkippedIfSymlinkIsMissing();
$file = $this->hookPath($repository, 'target-symlink');
@@ -141,7 +160,8 @@ public function testSet_Existing_ThrowsLogicException($repository)
{
$repository->getHooks()->set('foo', 'bar');
- $this->expectException('LogicException');
+ $this->expectException(LogicException::class);
+
$repository->getHooks()->set('foo', 'bar');
}
@@ -154,15 +174,21 @@ public function testRemove($repository)
touch($file);
$repository->getHooks()->remove('foo');
- $this->assertFalse(file_exists($file));
+
+ if (method_exists($this, 'assertFileDoesNotExist')) {
+ $this->assertFileDoesNotExist($file);
+ } else {
+ $this->assertFileNotExists($file);
+ }
}
/**
* @dataProvider provideFoobar
- * @expectedException LogicException
*/
public function testRemove_NotExisting_ThrowsLogicException($repository)
{
+ $this->expectException(LogicException::class);
+
$repository->getHooks()->remove('foo');
}
diff --git a/tests/Gitonomy/Git/Tests/LogTest.php b/tests/Gitonomy/Git/Tests/LogTest.php
index 2f123f9e..a02184dd 100644
--- a/tests/Gitonomy/Git/Tests/LogTest.php
+++ b/tests/Gitonomy/Git/Tests/LogTest.php
@@ -12,6 +12,8 @@
namespace Gitonomy\Git\Tests;
+use Gitonomy\Git\Parser\LogParser;
+
class LogTest extends AbstractTest
{
/**
@@ -22,8 +24,8 @@ public function testRevisionAndPath($repository)
$logReadme = $repository->getLog(self::LONGFILE_COMMIT, 'README');
$logImage = $repository->getLog(self::LONGFILE_COMMIT, 'image.jpg');
- $this->assertEquals(3, count($logReadme));
- $this->assertEquals(2, count($logImage));
+ $this->assertCount(3, $logReadme);
+ $this->assertCount(2, $logImage);
}
/**
@@ -35,7 +37,7 @@ public function testGetCommits($repository)
$commits = $log->getCommits();
- $this->assertEquals(3, count($commits), '3 commits in log');
+ $this->assertCount(3, $commits, '3 commits in log');
$this->assertEquals(self::LONGFILE_COMMIT, $commits[0]->getHash(), 'First is requested one');
$this->assertEquals(self::BEFORE_LONGFILE_COMMIT, $commits[1]->getHash(), "Second is longfile parent\'s");
}
@@ -76,4 +78,59 @@ public function testIterable($repository)
}
}
}
+
+ public function testFirstMessageEmpty()
+ {
+ $repository = $this->createEmptyRepository(false);
+ $repository->run('config', ['--local', 'user.name', '"Unit Test"']);
+ $repository->run('config', ['--local', 'user.email', '"unit_test@unit-test.com"']);
+
+ // Edge case: first commit lacks a message.
+ file_put_contents($repository->getWorkingDir().'/file', 'foo');
+ $repository->run('add', ['.']);
+ $repository->run('commit', ['--allow-empty-message', '--no-edit']);
+
+ $commits = $repository->getLog()->getCommits();
+ $this->assertCount(1, $commits);
+ }
+
+ public function testParsesCommitsWithAndWithoutGitButlerHeaders(): void
+ {
+ $logContent = <<<'EOT'
+ commit 1111111111111111111111111111111111111111
+ tree abcdefabcdefabcdefabcdefabcdefabcdefabcd
+ author John Doe 1620000000 +0000
+ committer John Doe 1620000000 +0000
+
+ First commit message
+
+ commit 2222222222222222222222222222222222222222
+ tree abcdefabcdefabcdefabcdefabcdefabcdefabcd
+ parent 1111111111111111111111111111111111111111
+ author Jane Smith 1620003600 +0000
+ committer Jane Smith 1620003600 +0000
+ gitbutler-headers-version: 2
+ gitbutler-change-id: a7bd485c-bae6-45b2-910f-163c78aace81
+
+ Commit with GitButler headers
+
+ commit 3333333333333333333333333333333333333333
+ tree abcdefabcdefabcdefabcdefabcdefabcdefabcd
+ author John Doe 1620007200 +0000
+ committer Jane Smith 1620007200 +0000
+
+ Another commit without GitButler headers
+
+ EOT;
+
+ $parser = new LogParser();
+ $parser->parse($logContent);
+
+ $log = $parser->log;
+ $this->assertCount(3, $log);
+
+ $this->assertEquals("First commit message\n", $log[0]['message']);
+ $this->assertEquals("Commit with GitButler headers\n", $log[1]['message']);
+ $this->assertEquals("Another commit without GitButler headers\n", $log[2]['message']);
+ }
}
diff --git a/tests/Gitonomy/Git/Tests/PushReferenceTest.php b/tests/Gitonomy/Git/Tests/PushReferenceTest.php
index 65969d9e..4490a3f9 100644
--- a/tests/Gitonomy/Git/Tests/PushReferenceTest.php
+++ b/tests/Gitonomy/Git/Tests/PushReferenceTest.php
@@ -52,7 +52,7 @@ public function testLog($repository)
$ref = new PushReference($repository, 'foo', self::INITIAL_COMMIT, self::LONGFILE_COMMIT);
$log = $ref->getLog()->getCommits();
- $this->assertEquals(7, count($log), '7 commits in log');
+ $this->assertCount(7, $log, '7 commits in log');
$this->assertEquals('add a long file', $log[0]->getShortMessage(), 'First commit is correct');
}
@@ -65,7 +65,7 @@ public function testSignedLog($repository)
{
$ref = new PushReference($repository, 'foo', self::INITIAL_COMMIT, self::SIGNED_COMMIT);
$log = $ref->getLog()->getCommits();
- $this->assertEquals(16, count($log), '16 commits in log');
+ $this->assertCount(16, $log, '16 commits in log');
$this->assertEquals('signed commit', $log[0]->getShortMessage(), 'Last commit is correct');
}
@@ -77,7 +77,7 @@ public function testLogWithExclude($repository)
$ref = new PushReference($repository, 'foo', PushReference::ZERO, self::LONGFILE_COMMIT);
$log = $ref->getLog([self::INITIAL_COMMIT])->getCommits();
- $this->assertEquals(7, count($log), '7 commits in log');
+ $this->assertCount(7, $log, '7 commits in log');
$this->assertEquals('add a long file', $log[0]->getShortMessage(), 'First commit is correct');
}
}
diff --git a/tests/Gitonomy/Git/Tests/ReferenceBagTest.php b/tests/Gitonomy/Git/Tests/ReferenceBagTest.php
new file mode 100644
index 00000000..f9b9c82c
--- /dev/null
+++ b/tests/Gitonomy/Git/Tests/ReferenceBagTest.php
@@ -0,0 +1,56 @@
+
+ * (c) Julien DIDIER
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Gitonomy\Git\Tests;
+
+use Gitonomy\Git\Repository;
+
+class ReferenceBagTest extends AbstractTest
+{
+ /**
+ * @dataProvider provideFoobar
+ */
+ public function testUnknownReference(Repository $repository)
+ {
+ $hash = $repository->getLog()->getSingleCommit()->getHash();
+
+ $repository->run('update-ref', ['refs/pipelines/1', $hash]);
+ $repository->run('update-ref', ['refs/merge-request/1/head', $hash]);
+ $repository->run('update-ref', ['refs/pull/1/head', $hash]);
+ $repository->run('update-ref', ['refs/notes/gtm-data', $hash]);
+
+ $refs = $repository->getReferences()->getAll();
+ if (method_exists($this, 'assertIsArray')) {
+ $this->assertIsArray($refs);
+ } else {
+ $this->assertInternalType('array', $refs);
+ }
+
+ // Check that at least it has the master ref
+ $this->assertArrayHasKey('refs/heads/master', $refs);
+
+ // Check that our custom refs have been ignored
+ $this->assertArrayNotHasKey('refs/pipelines/1', $refs);
+ $this->assertArrayNotHasKey('refs/merge-request/1/head', $refs);
+ $this->assertArrayNotHasKey('refs/pull/1/head', $refs);
+ $this->assertArrayNotHasKey('refs/notes/gtm-data', $refs);
+ }
+
+ /**
+ * @dataProvider provideEmpty
+ */
+ public function testEmptyRepo(Repository $repository)
+ {
+ $refs = $repository->getReferences()->getAll();
+ $this->assertSame([], $refs);
+ }
+}
diff --git a/tests/Gitonomy/Git/Tests/ReferenceTest.php b/tests/Gitonomy/Git/Tests/ReferenceTest.php
index 45b626e6..0f975577 100644
--- a/tests/Gitonomy/Git/Tests/ReferenceTest.php
+++ b/tests/Gitonomy/Git/Tests/ReferenceTest.php
@@ -12,13 +12,12 @@
namespace Gitonomy\Git\Tests;
+use Gitonomy\Git\Exception\ReferenceNotFoundException;
use Gitonomy\Git\Reference\Branch;
use Gitonomy\Git\Reference\Tag;
class ReferenceTest extends AbstractTest
{
- private $references;
-
/**
* @dataProvider provideEmpty
*/
@@ -35,7 +34,7 @@ public function testGetBranch($repository)
{
$branch = $repository->getReferences()->getBranch('master');
- $this->assertTrue($branch instanceof Branch, 'Branch object is correct type');
+ $this->assertInstanceOf(Branch::class, $branch, 'Branch object is correct type');
$this->assertEquals($branch->getCommitHash(), $branch->getCommit()->getHash(), 'Hash is correctly resolved');
}
@@ -59,11 +58,12 @@ public function testHasTag($repository)
/**
* @dataProvider provideFoobar
- * @expectedException Gitonomy\Git\Exception\ReferenceNotFoundException
*/
public function testGetBranch_NotExisting_Error($repository)
{
- $branch = $repository->getReferences()->getBranch('notexisting');
+ $this->expectException(ReferenceNotFoundException::class);
+
+ $repository->getReferences()->getBranch('notexisting');
}
/**
@@ -73,7 +73,7 @@ public function testGetTag($repository)
{
$tag = $repository->getReferences()->getTag('0.1');
- $this->assertTrue($tag instanceof Tag, 'Tag object is correct type');
+ $this->assertInstanceOf(Tag::class, $tag, 'Tag object is correct type');
$this->assertFalse($tag->isAnnotated(), 'Tag is not annotated');
$this->assertEquals(self::LONGFILE_COMMIT, $tag->getCommitHash(), 'Commit hash is correct');
@@ -87,7 +87,7 @@ public function testAnnotatedTag($repository)
{
$tag = $repository->getReferences()->getTag('annotated');
- $this->assertTrue($tag instanceof Tag, 'Tag object is correct type');
+ $this->assertInstanceOf(Tag::class, $tag, 'Tag object is correct type');
$this->assertTrue($tag->isAnnotated(), 'Tag is annotated');
$this->assertFalse($tag->isSigned(), 'Tag is not signed');
@@ -97,15 +97,23 @@ public function testAnnotatedTag($repository)
$this->assertEquals('heading', $tag->getSubjectMessage(), 'Message heading is correct');
$this->assertEquals("body\nbody", $tag->getBodyMessage(), 'Message body is correct');
+
+ $closure = function () {
+ return parent::getCommit();
+ };
+ $parentCommit = $closure->bindTo($tag, Tag::class);
+ $this->assertNotEquals($parentCommit()->getHash(), $tag->getCommit()->getHash(), 'Tag commit is not the same as main commit');
+ $this->assertEquals('fbde681b329a39e08b63dc54b341a3274c0380c0', $tag->getCommit()->getHash(), 'Tag commit is correct');
}
/**
* @dataProvider provideFoobar
- * @expectedException Gitonomy\Git\Exception\ReferenceNotFoundException
*/
public function testGetTag_NotExisting_Error($repository)
{
- $branch = $repository->getReferences()->getTag('notexisting');
+ $this->expectException(ReferenceNotFoundException::class);
+
+ $repository->getReferences()->getTag('notexisting');
}
/**
@@ -116,8 +124,8 @@ public function testResolve($repository)
$commit = $repository->getReferences()->getTag('0.1')->getCommit();
$resolved = $repository->getReferences()->resolve($commit->getHash());
- $this->assertEquals(1, count($resolved), '1 revision resolved');
- $this->assertTrue(reset($resolved) instanceof Tag, 'Resolved object is a tag');
+ $this->assertCount(1, $resolved, '1 revision resolved');
+ $this->assertInstanceOf(Tag::class, reset($resolved), 'Resolved object is a tag');
}
/**
@@ -128,8 +136,8 @@ public function testResolveTags($repository)
$commit = $repository->getReferences()->getTag('0.1')->getCommit();
$resolved = $repository->getReferences()->resolveTags($commit->getHash());
- $this->assertEquals(1, count($resolved), '1 revision resolved');
- $this->assertTrue(reset($resolved) instanceof Tag, 'Resolved object is a tag');
+ $this->assertCount(1, $resolved, '1 revision resolved');
+ $this->assertInstanceOf(Tag::class, reset($resolved), 'Resolved object is a tag');
}
/**
@@ -142,12 +150,12 @@ public function testResolveBranches($repository)
$resolved = $repository->getReferences()->resolveBranches($master->getCommitHash());
if ($repository->isBare()) {
- $this->assertEquals(1, count($resolved), '1 revision resolved');
+ $this->assertCount(1, $resolved, '1 revision resolved');
} else {
- $this->assertEquals(2, count($resolved), '2 revision resolved');
+ $this->assertCount(2, $resolved, '2 revision resolved');
}
- $this->assertTrue(reset($resolved) instanceof Branch, 'Resolved object is a branch');
+ $this->assertInstanceOf(Branch::class, reset($resolved), 'Resolved object is a branch');
}
/**
@@ -201,4 +209,32 @@ public function testCreateAndDeleteBranch($repository)
$branch->delete();
$this->assertFalse($references->hasBranch('foobar'), 'Branch foobar removed');
}
+
+ /**
+ * @dataProvider provideFoobar
+ */
+ public function testIsBranchMergedToMaster()
+ {
+ $repository = self::createFoobarRepository(false);
+
+ $repository->run('config', ['--local', 'user.name', '"Unit Test"']);
+ $repository->run('config', ['--local', 'user.email', '"unit_test@unit-test.com"']);
+
+ $master = $repository->getReferences()->getBranch('master');
+ $references = $repository->getReferences();
+
+ $branch = $references->createBranch('foobar-new', $master->getCommit()->getHash());
+
+ $this->assertTrue($branch->isMergedTo('master'));
+
+ $wc = $repository->getWorkingCopy();
+ $wc->checkout('foobar-new');
+
+ $file = $repository->getWorkingDir().'/foobar-test.txt';
+ file_put_contents($file, 'test');
+ $repository->run('add', [$file]);
+ $repository->run('commit', ['-m', 'foobar-test.txt updated']);
+
+ $this->assertFalse($branch->isMergedTo('master'));
+ }
}
diff --git a/tests/Gitonomy/Git/Tests/RepositoryTest.php b/tests/Gitonomy/Git/Tests/RepositoryTest.php
index d00a102e..fc5c7deb 100644
--- a/tests/Gitonomy/Git/Tests/RepositoryTest.php
+++ b/tests/Gitonomy/Git/Tests/RepositoryTest.php
@@ -13,19 +13,28 @@
namespace Gitonomy\Git\Tests;
use Gitonomy\Git\Blob;
+use Gitonomy\Git\Exception\RuntimeException;
use Prophecy\Argument;
+use Prophecy\PhpUnit\ProphecyTrait;
class RepositoryTest extends AbstractTest
{
+ use ProphecyTrait;
+
/**
* @dataProvider provideFoobar
*/
- public function testGetBlob_WithExisting_Works($repository)
+ public function testGetBlobWithExistingWorks($repository)
{
$blob = $repository->getCommit(self::LONGFILE_COMMIT)->getTree()->resolvePath('README.md');
- $this->assertTrue($blob instanceof Blob, 'getBlob() returns a Blob object');
- $this->assertContains('Foo Bar project', $blob->getContent(), 'file is correct');
+ $this->assertInstanceOf(Blob::class, $blob, 'getBlob() returns a Blob object');
+
+ if (method_exists($this, 'assertStringContainsString')) {
+ $this->assertStringContainsString('Foo Bar project', $blob->getContent(), 'file is correct');
+ } else {
+ $this->assertContains('Foo Bar project', $blob->getContent(), 'file is correct');
+ }
}
/**
@@ -34,7 +43,8 @@ public function testGetBlob_WithExisting_Works($repository)
public function testGetSize($repository)
{
$size = $repository->getSize();
- $this->assertGreaterThan(70, $size, 'Repository is greater than 70KB');
+ $this->assertGreaterThanOrEqual(57, $size, 'Repository is at least 57KB');
+ $this->assertLessThan(84, $size, 'Repository is less than 84KB');
}
public function testIsBare()
@@ -78,7 +88,6 @@ public function testLoggerOk($repository)
/**
* @dataProvider provideFoobar
- * @expectedException RuntimeException
*/
public function testLoggerNOk($repository)
{
@@ -86,6 +95,8 @@ public function testLoggerNOk($repository)
$this->markTestSkipped();
}
+ $this->expectException(RuntimeException::class);
+
$loggerProphecy = $this->prophesize('Psr\Log\LoggerInterface');
$loggerProphecy
->info(Argument::type('string'))
diff --git a/tests/Gitonomy/Git/Tests/RevisionTest.php b/tests/Gitonomy/Git/Tests/RevisionTest.php
index 2f8428df..84711843 100644
--- a/tests/Gitonomy/Git/Tests/RevisionTest.php
+++ b/tests/Gitonomy/Git/Tests/RevisionTest.php
@@ -13,6 +13,7 @@
namespace Gitonomy\Git\Tests;
use Gitonomy\Git\Commit;
+use Gitonomy\Git\Exception\ReferenceNotFoundException;
use Gitonomy\Git\Log;
use Gitonomy\Git\Revision;
@@ -25,23 +26,24 @@ public function testGetCommit($repository)
{
$revision = $repository->getRevision(self::LONGFILE_COMMIT.'^');
- $this->assertTrue($revision instanceof Revision, 'Revision object type');
+ $this->assertInstanceOf(Revision::class, $revision, 'Revision object type');
$commit = $revision->getCommit();
- $this->assertTrue($commit instanceof Commit, 'getCommit returns a Commit');
+ $this->assertInstanceOf(Commit::class, $commit, 'getCommit returns a Commit');
$this->assertEquals(self::BEFORE_LONGFILE_COMMIT, $commit->getHash(), 'Resolution is correct');
}
/**
* @dataProvider provideFoobar
- * @expectedException Gitonomy\Git\Exception\ReferenceNotFoundException
- * @expectedExceptionMessage Can not find revision "non-existent-commit"
*/
public function testGetFailingReference($repository)
{
- $revision = $repository->getRevision('non-existent-commit')->getCommit();
+ $this->expectException(ReferenceNotFoundException::class);
+ $this->expectExceptionMessage('Can not find revision "non-existent-commit"');
+
+ $repository->getRevision('non-existent-commit')->getCommit();
}
/**
@@ -53,7 +55,7 @@ public function testGetLog($repository)
$log = $revision->getLog(null, 2, 3);
- $this->assertTrue($log instanceof Log, 'Log type object');
+ $this->assertInstanceOf(Log::class, $log, 'Log type object');
$this->assertEquals(2, $log->getOffset(), 'Log offset is passed');
$this->assertEquals(3, $log->getLimit(), 'Log limit is passed');
$this->assertEquals([$revision], $log->getRevisions()->getAll(), 'Revision is passed');
diff --git a/tests/Gitonomy/Git/Tests/TreeTest.php b/tests/Gitonomy/Git/Tests/TreeTest.php
index e6cbc025..b7ba7d32 100644
--- a/tests/Gitonomy/Git/Tests/TreeTest.php
+++ b/tests/Gitonomy/Git/Tests/TreeTest.php
@@ -13,6 +13,7 @@
namespace Gitonomy\Git\Tests;
use Gitonomy\Git\Blob;
+use Gitonomy\Git\CommitReference;
class TreeTest extends AbstractTest
{
@@ -21,19 +22,57 @@ class TreeTest extends AbstractTest
/**
* @dataProvider provideFooBar
*/
- public function testCase($repository)
+ public function testGetEntries($repository)
{
$tree = $repository->getCommit(self::LONGFILE_COMMIT)->getTree();
$entries = $tree->getEntries();
- $this->assertTrue(isset($entries['long.php']), 'long.php is present');
+ $this->assertNotEmpty($entries['long.php'], 'long.php is present');
$this->assertTrue($entries['long.php'][1] instanceof Blob, 'long.php is a Blob');
- $this->assertTrue(isset($entries['README.md']), 'README.md is present');
+ $this->assertNotEmpty($entries['README.md'], 'README.md is present');
$this->assertTrue($entries['README.md'][1] instanceof Blob, 'README.md is a Blob');
}
+ /**
+ * @dataProvider provideFooBar
+ */
+ public function testGetCommitReferenceEntries($repository)
+ {
+ $tree = $repository->getCommit(self::NO_MESSAGE_COMMIT)->getTree();
+
+ $commits = $tree->getCommitReferenceEntries();
+
+ $this->assertNotEmpty($commits['barbaz'], 'barbaz is present');
+ $this->assertTrue($commits['barbaz'][1] instanceof CommitReference, 'barbaz is a Commit');
+ }
+
+ /**
+ * @dataProvider provideFooBar
+ */
+ public function testGetTreeEntries($repository)
+ {
+ $tree = $repository->getCommit(self::NO_MESSAGE_COMMIT)->getTree();
+
+ $trees = $tree->getTreeEntries();
+
+ $this->assertEmpty($trees);
+ }
+
+ /**
+ * @dataProvider provideFooBar
+ */
+ public function testGetBlobEntries($repository)
+ {
+ $tree = $repository->getCommit(self::NO_MESSAGE_COMMIT)->getTree();
+
+ $blobs = $tree->getBlobEntries();
+
+ $this->assertNotEmpty($blobs['README.md'], 'README.md is present');
+ $this->assertTrue($blobs['README.md'][1] instanceof Blob, 'README.md is a blob');
+ }
+
/**
* @dataProvider provideFooBar
*/
@@ -45,6 +84,6 @@ public function testResolvePath($repository)
$resolved = $tree->resolvePath($path);
$entries = $resolved->getEntries();
- $this->assertTrue(isset($entries['d']), 'Successfully resolved source folder');
+ $this->assertNotEmpty($entries['d'], 'Successfully resolved source folder');
}
}
diff --git a/tests/Gitonomy/Git/Tests/WorkingCopyTest.php b/tests/Gitonomy/Git/Tests/WorkingCopyTest.php
index a26002f5..dfa048f9 100644
--- a/tests/Gitonomy/Git/Tests/WorkingCopyTest.php
+++ b/tests/Gitonomy/Git/Tests/WorkingCopyTest.php
@@ -13,15 +13,16 @@
namespace Gitonomy\Git\Tests;
use Gitonomy\Git\Admin;
+use Gitonomy\Git\Exception\LogicException;
+use Gitonomy\Git\Exception\RuntimeException;
use Gitonomy\Git\Reference\Branch;
class WorkingCopyTest extends AbstractTest
{
- /**
- * @expectedException LogicException
- */
public function testNoWorkingCopyInBare()
{
+ $this->expectException(LogicException::class);
+
$path = self::createTempDir();
$repo = Admin::init($path, true, self::getOptions());
@@ -35,7 +36,7 @@ public function testCheckout()
$wc->checkout('origin/new-feature', 'new-feature');
$head = $repository->getHead();
- $this->assertTrue($head instanceof Branch, 'HEAD is a branch');
+ $this->assertInstanceOf(Branch::class, $head, 'HEAD is a branch');
$this->assertEquals('new-feature', $head->getName(), 'HEAD is branch new-feature');
}
@@ -70,11 +71,10 @@ public function testDiffPending()
$this->assertCount(1, $diffPending->getFiles());
}
- /**
- * @expectedException RuntimeException
- */
public function testCheckoutUnexisting()
{
+ $this->expectException(RuntimeException::class);
+
self::createFoobarRepository(false)->getWorkingCopy()->checkout('foobar');
}