Giter VIP home page Giter VIP logo

gitelephant's Introduction

GitElephant

Latest Stable Version License Total Downloads Montly Downloads

Build Status Code Check Dependency Status Scrutinizer Quality Score Code Coverage SensioLabsInsight

GitElephant is an abstraction layer to manage your git repositories with php

This library officially supports git >= 1.8, older version are supported as well, but with some caveat.

How it works

GitElephant mostly rely on the git binary to retrieve information about the repository, read the output and create an OOP layer to interact with

Some parts are (or will be) implemented by reading directly inside the .git folder

The api is completely transparent to the end user. You don't have to worry about which method is used.

Requirements

  • php >= 7.2
  • *nix system with git installed

*For php 7.1, please use GitElephant version 3.x

*For php 7.0, please use GitElephant version 2.x

*For php 5.x please use GitElephant version 1.x

This library is tested on linux, but it should work well with any unix system, as far as a git binary is available. For windows support, well.. if someone want to help?!

Installation

composer

To install GitElephant with composer you simply need to create a composer.json in your project root and add:

{
    "require": {
        "cypresslab/gitelephant": "~4.0"
    }
}

Then run

$ curl -s https://getcomposer.org/installer | php
$ composer install

You have now GitElephant installed in vendor/cypresslab/gitelephant

And an handy autoload file to include in you project in vendor/autoload.php

How to use

use GitElephant\Repository;
$repo = new Repository('/path/to/git/repository');
// or the factory method
$repo = Repository::open('/path/to/git/repository');

By default GitElephant try to use the git binary on your system.

the Repository class is the main class where you can find every method you need...

Read repository

// get the current status
$repo->getStatusOutput(); // returns an array of lines of the status message

branches

$repo->getBranches(); // return an array of Branch objects
$repo->getMainBranch(); // return the Branch instance of the current checked out branch
$repo->getBranch('master'); // return a Branch instance by its name
$develop = Branch::checkout($repo, 'develop');
$develop = Branch::checkout($repo, 'develop', true); // create and checkout

tags

$repo->getTags(); // array of Tag instances
$repo->getTag('v1.0'); // a Tag instance by name
Tag::pick($repo, 'v1.0'); // a Tag instance by name

// last tag by date
$repo->getLastTag();

commits

$repo->getCommit(); // get a Commit instance of the current HEAD
$repo->getCommit('v1.0'); // get a Commit instance for a tag
$repo->getCommit('1ac370d'); // full sha or part of it
// or directly create a commit object
$commit = new Commit($repo, '1ac370d');
$commit = new Commit($repo, '1ac370d'); // head commit

// count commits
$repo->countCommits('1ac370d'); // number of commits to arrive at 1ac370d
// commit is countable, so, with a commit object, you can do
$commit->count();
// as well as
count($commit);

remotes

$repo->getRemote('origin'); // a Remote object
$repo->getRemotes(); // array of Remote objects

// Log contains a collection of commit objects
// syntax: getLog(<tree-ish>, path = null, limit = 15, offset = null)
$log = $repo->getLog();
$log = $repo->getLog('master', null, 5);
$log = $repo->getLog('v0.1', null, 5, 10);
// or directly create a log object
$log = new Log($repo);
$log = new Log($repo, 'v0.1', null, 5, 10);

// countable
$log->count();
count($log);

// iterable
foreach ($log as $commit) {
    echo $commit->getMessage();
}

status

If you build a GitElephant\Status\Status class, you will get a nice api for getting the actual state of the working tree and staging area.

$status = $repo->getStatus();
$status = GitElephant\Status\Status::get($repo); // it's the same...

$status->all(); // A Sequence of StatusFile objects
$status->untracked();
$status->modified();
$status->added();
$status->deleted();
$status->renamed();
$status->copied();

all this methods returns a Sequence of StatusFile objects, credit to PhpCollection

a StatusFile instance has all the information about the tree node changes. File names (and new file names for renamed objects), index and working tree status, and also a "git style" description like: added to index or deleted in work tree

Manage repository

You could also use GitElephant to manage your git repositories via PHP.

Your web server user (like www-data) needs to have access to the folder of the git repository

$repo->init(); // init
$repo->cloneFrom("git://github.com/matteosister/GitElephant.git"); // clone

// stage changes
$repo->stage('file1.php');
$repo->stage(); // stage all

// commit
$repo->commit('my first commit');
$repo->commit('my first commit', true); // commit and stage every pending changes in the working tree

// remotes
$repo->addRemote('awesome', 'git://github.com/matteosister/GitElephant.git');

// checkout
$repo->checkout($repo->getTag('v1.0')); // checkout a tag
$repo->checkout('master'); // checkout master

// manage branches
$repo->createBranch('develop'); // create a develop branch from current checked out branch
$repo->createBranch('develop', 'master'); // create a develop branch from master
$repo->deleteBranch('develop'); // delete the develop branch
$repo->checkoutAllRemoteBranches('origin'); // checkout all the branches from the remote repository

// manage tags
// create  a tag named v1.0 from master with the given tag message
$repo->createTag('v1.0', 'master', 'my first release!');
// create  a tag named v1.0 from the current checked out branch with the given tag message
$repo->createTag('v1.0', null, 'my first release!');
// create a tag from a Commit object
$repo->createTag($repo->getCommit());

Remote repositories

If you need to access remote repository you have to install the ssh2 extension and pass a new Caller to the repository. this is a new feature...consider this in a testing phase

$repo = new Repository('/path/to/git/repository');
$connection = ssh_connect('host', 'port');
// authorize the connection with the method you want
ssh2_auth_password($connection, 'user', 'password');
$caller = new CallerSSH2($connection, '/path/to/git/binary/on/server');
$repo = Repository::open('/path/to/git/repository');
$repo->setCaller($caller);

A versioned tree of files

A git repository is a tree structure versioned in time. So if you need to represent a repository in a, let's say, web browser, you will need a tree representation of the repository, at a given point in history.

Tree class

$tree = $repo->getTree(); // retrieve the actual *HEAD* tree
$tree = $repo->getTree($repo->getCommit('1ac370d')); // retrieve a tree for a given commit
$tree = $repo->getTree('master', 'lib/vendor'); // retrieve a tree for a given path
// generate a tree
$tree = new Tree($repo);

The Tree class implements ArrayAccess, Countable and Iterator interfaces.

You can use it as an array of git objects.

foreach ($tree as $treeObject) {
    echo $treeObject;
}

A Object instance is a php representation of a node in a git tree

echo $treeObject; // the name of the object (folder, file or link)
$treeObject->getType(); // one class constant of Object::TYPE_BLOB, Object::TYPE_TREE and Object::TYPE_LINK
$treeObject->getSha();
$treeObject->getSize();
$treeObject->getName();
$treeObject->getSize();
$treeObject->getPath();

You can also pass a tree object to the repository to get its subtree

$subtree = $repo->getTree('master', $treeObject);

Diffs

If you want to check a Diff between two commits the Diff class comes in

// get the diff between the given commit and it parent
$diff = $repo->getDiff($repo->getCommit());
// get the diff between two commits
$diff = $repo->getDiff($repo->getCommit('1ac370d'), $repo->getCommit('8fb7281'));
// same as before for a given path
$diff = $repo->getDiff($repo->getCommit('1ac370d'), $repo->getCommit('8fb7281'), 'lib/vendor');
// or even pass a Object
$diff = $repo->getDiff($repo->getCommit('1ac370d'), $repo->getCommit('8fb7281'), $treeObject);
// alternatively you could directly use the sha of the commit
$diff = $repo->getDiff('1ac370d', '8fb7281');
// manually generate a Diff object
$diff = Diff::create($repo); // defaults to the last commit
// or as explained before
$diff = Diff::create($repo, '1ac370d', '8fb7281');

The Diff class implements ArrayAccess, Countable and Iterator interfaces

You can iterate over DiffObject

foreach ($diff as $diffObject) {
    // mode is a constant of the DiffObject class
    // DiffObject::MODE_INDEX an index change
    // DiffObject::MODE_MODE a mode change
    // DiffObject::MODE_NEW_FILE a new file change
    // DiffObject::MODE_DELETED_FILE a deleted file change
    echo $diffObject->getMode();
}

A DiffObject is a class that implements ArrayAccess, Countable and Iterator interfaces. It represent a file, folder or submodule changed in the Diff.

Every DiffObject can have multiple chunks of changes. For example:

    added 3 lines at line 20
    deleted 4 lines at line 560

You can iterate over DiffObject to get DiffChunks. DiffChunks are the last steps of the Diff process, they are a collection of DiffChunkLine Objects

foreach ($diffObject as $diffChunk) {
    if (count($diffChunk) > 0) {
        echo "change detected from line ".$diffChunk->getDestStartLine()." to ".$diffChunk->getDestEndLine();
        foreach ($diffChunk as $diffChunkLine) {
            echo $diffChunkLine; // output the line content
        }
    }
}

Testing

The library is fully tested with PHPUnit.

Go to the base library folder and install the dev dependencies with composer, and then run the phpunitt test suite

$ composer --dev install
$ ./vendor/bin/phpunit # phpunit test suite

If you want to run the test suite you should have all the dependencies loaded.

Symfony

There is a GitElephantBundle to use this library inside a Symfony project.

Dependencies

for tests

Code style

GitElephant follows the:

Want to contribute?

You are my new hero!

Just remember:

  • PSR coding standards
  • add tests to everything you develop
  • if you don't use gitflow, just remember to branch from "develop" and send your PR there. Please do not send pull requests on the master branch.

Author

Matteo Giachino (twitter)

Many thanks to all the contributors

Thanks

Many thanks to Linus and all those who have worked/contributed in any way to git. Because it's awesome!!! I can't imagine being a developer without it.

Logo design by Stefano Lodovico

Analytics

gitelephant's People

Contributors

adamlacoste avatar bitdeli-chef avatar bukharovsi avatar c9s avatar clemens-tolboom avatar davidneimeyer avatar drupol avatar fntlnz avatar geek-merlin avatar gemorroj avatar genietim avatar imunhatep avatar jameshalsall avatar jmsbot avatar john-schlick avatar jurgenhaas avatar maff avatar marcj avatar martijnbraam avatar mathroc avatar matteosister avatar nolwennig avatar ocubom avatar programaths avatar qpleple avatar redian avatar rvitaliy avatar tobias-kuendig avatar vivalldi avatar yunwuxin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gitelephant's Issues

Add support for --work-tree to make bare repos useful

I am in the process of porting my build & deployment process from a series of shell scripts to PHP, and using GitElephant to handle the heavy lifting of git interactions involved. However one current weakness of GitElephant is that it does not support the --work-tree option which allows you to explicitly set a working directory for your command. This option is especially useful when working with a bare repo, which does not have a working directory of its own.

However, after reviewing the design of GitElephant I find that there is no obvious (to me) place to "plug in" this functionality, and so before I start work on the feature I'd like to consult with @matteosister on the preferred way to handle this. I have a few suggested approaches, but I'm open to others.

Alternative Solutions:

  1. Make it a param on all or most Repository methods
    This option kind of sucks, but I'm including it for the sake of completeness. Basically add a string param to all relevant Repository methods and their associated XCommand counterparts to allow a user to set a --work-tree. The reason this sucks is that some Repository methods already have many params and this is a feature that is only useful for a small percentage of use cases. Future params tacked on to these methods then have to come after this mostly-ignored param, making it a useless hurdle for most users. Also just contributes to param bloat in general.

  2. Make it a Repository property, then pass it to Commands
    This sucks slightly less than Solution 1. Here we leave the Repository method params alone for the most part, only adding work tree as a param to methods which create a Repository object (__construct, open, createFromRemote) which is set as a property. Then any relevant Repository methods can check that property and include it as a param when calling their associated XCommand counterparts. Limits param bloat in Repository, just adds it to the commands. Still not great, keeps many of the problems from Solution 1, but at least keeps them out of the main Repository class.

  3. Make it a Repository property and manipulate strings returned from Commands
    This is a slightly more elegant solution, though it still involves string manipulation which can be kind of murky territory. So we have a Repository property, $workTree, set in the manner described in Solution 2. Any Repository method which interacts with the working directory can check this property and if it is set, call the associated XCommand and then str_replace its output so that the first instance of "git" is replaced with "git '--work-tree=/foo/bar'", then pass the result of that into $this->caller->execute(). Chances are that we'd want to create some utility method in Repository to handle this replacement, since it's going to be reused a lot throughout the class.

As you can probably tell, I favor Solution 3. Please weigh in on which approach you think is best and I can get started coding.

Implement "reset" command (solution code included)

In order to implement the reset command, in Repository add the following use command:
use GitElephant\Command\ResetCommand;

Now add the following function to Repository:
/**
* Reset this branch of the repository
*
* @param string $option usually --hard
* @param tag $the tag to use as the reference for the reset
*
* @throws \RuntimeException
* @throws \InvalidArgumentException
* @throws \Symfony\Component\Process\Exception\RuntimeException
* @return Repository
*/
public function reset($option = null, $tag = null)
{
$this->caller->execute(ResetCommand::getInstance()->reset($option, $tag));

    return $this;
}

and add the following file to the Command directory using hte name ResetCommand.php for the filename:

clearAll(); $this->addCommandName(self::GIT_RESET_COMMAND); // if there is an option add it. if (!is_null($option)) { $this->addCommandSubject($option); if (!is_null($tagOrCommit)) { $this->addCommandSubject2($tagOrCommit); } } return $this->getCommand(); } }

$repo->getBranches(true, true) fails. (Includes solution code)

Getting the actual repository object as well as ALL of them
This is due to the remotes/origin/HEAD line not fitting the format in
Branches::getMatches

So, I suck at generating pull requests, adn I'm pretty sure that I don't have privs in this repository anyway...
The fix is threefold:

In Repository->getBranches
if (nameonly)
else
foreach

add
// Don't try to create the remotes origin/HEAD branch
$reducedLine = trim(preg_replace("/\s+/", " ", $branchLine));
$branchParts = explode(" ", $reducedLine);
if ($branchParts[1] === "->") {
continue;
}

then in
Branches::getMatch add these as the first four lines of the function:
// Remotes origin/HEAD causes us problems.
$reducedString = trim(preg_replace("/\s+/", " ", $branchString));
$branchParts = explode(" ", $reducedString);
if ($branchParts[1] === "->") {
return array(null, null, null, null);
}

then in Branches->createFromCommand (private function)
add the first line
$all = true;
and modify the second line to read:
$command = BranchCommand::getInstance()->lists($all);

The other thing that might be nice is to modify Repository->getBranch to be able to take one of these remote names, and that would require it looking like:
public function getBranch($name, $all=false)
{
/** @var Branch $branch */
$nameOnly = false;
foreach ($this->getBranches($nameOnly, $all) as $branch) {
if ($branch->getName() == $name) {
return $branch;
}
}

    return null;
}

And as a side note, what I'm really after is being able to merge a branch against origin/master, and it appears that until this is there, I can't even try to do that.
Note: to get a merge of origin the refs/heads/ in the fullRef needs to go away.
What I'm doing for now is a $repo->setFullRef('origin/master');
before the merge, and this gets me the merge command I want (which works). But it's clearly NOT a clean solution. Probably a parameter to the merge command is in order to allow this to work cleanly.

And merges with conflicts result in throwing an exception.
I'd prefer if you changed Repository->merge to lookj like
$git = true;
$cwd = null;
$acceptedExitCodes = array(0, 1);
$this->caller->execute(MergeCommand::getInstance()->merge($branch), $git, $cwd, $acceptedExitCodes);

    return $this;

so that I can do a getCaller and take a look to see if there are conflicts. But the status might also get me that.

If you want to get ahold of me, please feel free to email me at [email protected]

Add support for git fetch --tags

Git fetch will only grab tags belonging to the commits it has fetched, so any tags pointing to commits which are not part of a branch on the remote will not be fetched by default.

The way around this is to use git fetch --tags, which pulls down all tags on the remote.

GitElephant can support this feature with a few small tweaks.

Show file contents at a particular commit

Here is what I'm trying to execute:

git show commit_ref:path_to_file.txt

It seems like there is no way of doing this with GetElephant at the moment. ShowCommand exists, but it is only applicable to a commit. Are you planning to implement something like $object->show() for blobs? Any temporal workaround?

My ultimate goal is to dump the state of the whole repository at a particular commit into another directory without making changes in a working copy. Maybe that can be done somehow else.

Cheers!

Hard time understanding how to use the SSH2 Remote implementation

Hello,

I am currently trying to fetch information of a repository on a remote server via ssh. However, there seem to be some issues, I can't get my head wrapped arround.

  1. When creating an instance of Repository, the constructor will always check, if the provided repository exists on the machine, the script is executed on

    if (!is_dir($repositoryPath)) {
    throw new InvalidRepositoryPathException($repositoryPath);
    }

This does not make sense, since you want to query information from a different machine. I believe this check should be moved to the Callerclass instead.

  1. When executing a command like log, the path of the repository seems to be not considered.

$repo->getTree(), for example, will result in the follwing command being executed on the remote machine via ssh git ls-tree '-l' 'HEAD'. The path of the repository is not being used here at all, which will cause everything to lead to empty results.

Here is the full code I am working with:

$connection = \ssh2_connect('remote.host', 22, array('hostkey'=>'ssh-rsa'));

// authorize the connection with the method you want
\ssh2_auth_pubkey_file(
$connection,
'user',
realpath(__DIR__ . '/../Resources/ssh/id_rsa.pub'),
realpath(__DIR__ . '/../Resources/ssh/id_rsa'),
''
);

$caller = new CallerSSH2($connection, 'git');
$repo = Repository::open('path/to/repository/on/remote/server');
$repo->setCaller($caller);
dump($repo->getTree());

Am I maybe just doing it wrong?

Allow user to force branch deletion

The command git branch -d will only delete a branch whose commits are included in the history of another branch. I would like to add support for the -D variant which deletes a branch without consideration for what references (if any) exist for a branch's commits.

prevent git from using wrong .git folder

use case

my
|--folder
      |--workspace
               |--project
                      |--data
  • my/folder/workspace/project location of a php project
  • my/folder/workspace/project/data gitignore'd folder, where I store different data using git

If I forgot to initialize the git repo under data and use GitElephant on that folder, git will automatically traverse upwards, until finding the first .git folder and will try to execute the commands on that repository.

I could not find an easy way fix this, since even when using the "-C" option, git still traverses upwards. One solution I could think of, would be to have Repository::open() validate, if the given folder actually contains a git repository, otherwise throwing an exception. In addition there would be an Repository::create()|init() method. This is obviously a BC break and might not be worth the hassle.

But since I fell into this trap just today, I wanted to at least report it 😄

Clone on windows

Hi,

I tried it to clone a project on windows, it needs to change:

const GIT_CLONE_COMMAND = 'clone';

to

const GIT_CLONE_COMMAND = 'git clone';

I think, maybe other GIT_XX_COMMAND need to be done the same thing on windows

$repo->GetTags is WAY too slow. Support for "git for-each-ref" is needed (solution code included)

$repository->getTags is VERY slow on repositories that have hundereds of tags. This is becasue each tag requires another system process to make the call to get it's sha.

If you do not need ALL the tags, it is much faster to use for-each-ref and have it get ALL the information that you need to create the tag without an additional exec. (In fact, I suspect this would be a better implementation for getTags in general due to speed.)

I have implemented $repository->getRecentTags($count)

Honestly, this implementation seems like it's just a hair out of line with what you normally do... You seem to have the parse (in the command) create the resultant object, I feel far more comfortable having the repository object directly do the create from the output of the parse, but it would be really easy to have the parse create and return a tag and then just add that to the $tagArray in the repository object if thats your preference.

Again, I'd do a pull request, but your unit tests don't run without more configuration than I have time for. They fail to start, with complaints about files not existing, and you insist that pull requests must have unit tests. Oh well.

So, instead, I'm including the code diff that makes this fix go, and go a LOT faster: (If you'd rather have a pull request, I'm happy to generate one, if you want me to email the files, I'm happy to do that. If you want just the code snippets, I'm also happy to give you that.)

jschlick@dvm-jschlick1:/usr/local/development-process-master/common/lib/GitElephant/vendor/cypresslab/gitelephant(BGS-1269)$ git diff origin/BGS-1269
diff --git a/common/lib/GitElephant/vendor/cypresslab/gitelephant/src/GitElephant/Command/ForEachRefCommand.php b/common/lib/GitElephant/vend
new file mode 100755
index 0000000..1243d52
--- /dev/null
+++ b/common/lib/GitElephant/vendor/cypresslab/gitelephant/src/GitElephant/Command/ForEachRefCommand.php
@@ -0,0 +1,135 @@
+<?php
+/**

  • * GitElephant - An abstraction layer for git written in PHP
  • * Copyright (C) 2014 John Schlick
  • * This program is free software: you can redistribute it and/or modify
  • * it under the terms of the GNU General Public License as published by
  • * the Free Software Foundation, either version 3 of the License, or
  • * (at your option) any later version.
  • * This program is distributed in the hope that it will be useful,
  • * but WITHOUT ANY WARRANTY; without even the implied warranty of
  • * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  • * GNU General Public License for more details.
  • * You should have received a copy of the GNU General Public License
  • * along with this program. If not, see [http://www.gnu.org/licenses/].
  • /
    +
    +namespace GitElephant\Command;
    +
    +use GitElephant\Objects\Tag;
    +use GitElephant\Objects\Commit;
    +use GitElephant\Exception\InvalidArgumentException;
    +
    +/
    *
  • * ForEachRef Command generator
  • * @author John Schlick [email protected]
  • */
    +class ForEachRefCommand extends BaseCommand
    +{
  • const GIT_FOR_EACH_REF = 'for-each-ref';
  • // The types of objects to take a look at.
  • const TYPE_TAGS = 'refs/tags';
  • const OPTION_SORT = '--sort=';
  • const OPTION_FORMAT = '--format=';
  • const OPTION_COUNT = '--count=';
  • // If there is no message, use this one.
  • const OPTION_SORT_COMMITTERDATE = '-committerdate';
  • // As long as there is always a message with the tag, then use this one.
  • const OPTION_SORT_TAGGERDATE = '-taggerdate';
  • // The ones with stars reach into the commit for their information.
  • const OPTION_FORMAT_OBJECTNAME = '%(*objectname)';
  • const OPTION_FORMAT_OBJECTTYPE = '%(objecttype)';
  • const OPTION_FORMAT_REFNAME = '%(refname)';
  • const OPTION_FORMAT_AUTHORDATE = '%(*authordate)';
  • const OPTION_FORMAT_SUBJECT = '%(*subject)';
  • // Constants for names for array indexed when we parse.
  • const COMPONENT_SHA = 'SHA'; // The objectname above.
  • const COMPONENT_TYPE = 'TYPE'; // The objecttype above.
  • const COMPONENT_NAME = 'NAME'; // the refname above.
  • const COMPONENT_DATE = 'DATE'; // The authordate above.
  • const COMPONENT_COMMENT = 'COMMENT'; // the subject above.
  • /**
  • \* @return RevListCommand
    
  • */
    
  • public static function getInstance()
  • {
  •    return new self();
    
  • }
  • /**
  • \* get output lines that make sense for the last n tags.
    
  • *
    
  • \* @param \GitElephant\Objects\Tag $tag a tag instance
    
  • *
    
  • \* @throws \RuntimeException
    
  • \* @return string
    
  • */
    
  • public function getRecentTags($count)
  • {
  •    $this->clearAll();
    
  •    $this->addCommandName(static::GIT_FOR_EACH_REF);
    
  •    $this->addCommandSubject(static::TYPE_TAGS);
    
  •    // specify it's sort order.
    
  •    $this->addCommandArgument(static::OPTION_SORT . static::OPTION_SORT_TAGGERDATE);
    
  •    // Specify the format of output we want.
    
  •    $format = static::OPTION_FORMAT . '"' .
    
  •        static::OPTION_FORMAT_OBJECTNAME .
    
  •        "#" .
    
  •        static::OPTION_FORMAT_OBJECTTYPE .
    
  •        "#" .
    
  •        static::OPTION_FORMAT_REFNAME .
    
  •        "#" .
    
  •        static::OPTION_FORMAT_AUTHORDATE .
    
  •        "#" .
    
  •        static::OPTION_FORMAT_SUBJECT .
    
  •        '"';
    
  •    $this->addCommandArgument($format);
    
  •    // Now add how many to get.
    
  •    $this->addCommandArgument(static::OPTION_COUNT . $count);
    
  •    return $this->getCommand();
    
  • }
  • /**
  • \* take a recent tag output line and break it into component parts.
    
  • *
    
  • \* @param string $tagString - the string to parse.
    
  • *
    
  • \* @throws \RuntimeException
    
  • \* @return array indexed by COMPONENT_xxx constants.
    
  • */
    
  • public function parseRecentTagString($tagString)
  • {
  •    $tagString = trim($tagString, '"');
    
  •    $tagComponents = explode('#', $tagString, 5);
    
  •    if (!is_array($tagComponents) || count($tagComponents) !== 5) {
    
  •        throw new InvalidArgumentException($repositoryPath);
    
  •    }
    
  •    // Make the array we will return.
    
  •    $namedTagComponents = array();
    
  •    $namedTagComponents[static::COMPONENT_SHA] = $tagComponents[0];
    
  •    $namedTagComponents[static::COMPONENT_TYPE] = $tagComponents[1];
    
  •    $namedTagComponents[static::COMPONENT_NAME] = $tagComponents[2];
    
  •    // Strip the refs/tags off the name so that we can JUST have the tagname.
    
  •    $prefix = 'refs/tags/';
    
  •    if (substr($namedTagComponents[static::COMPONENT_NAME], 0, strlen($prefix)) === $prefix) {
    
  •        $namedTagComponents[static::COMPONENT_NAME] = substr($namedTagComponents[static::COMPONENT_NAME], strlen($prefix));
    
  •    }
    
  •    $namedTagComponents[static::COMPONENT_DATE] = $tagComponents[3];
    
  •    $namedTagComponents[static::COMPONENT_COMMENT] = $tagComponents[4];
    
  •    return $namedTagComponents;
    
  • }
    +}
    diff --git a/common/lib/GitElephant/vendor/cypresslab/gitelephant/src/GitElephant/Objects/Tag.php b/common/lib/GitElephant/vendor/cypresslab/
    old mode 100644
    new mode 100755
    index e6a6018..681b142
    --- a/common/lib/GitElephant/vendor/cypresslab/gitelephant/src/GitElephant/Objects/Tag.php
    +++ b/common/lib/GitElephant/vendor/cypresslab/gitelephant/src/GitElephant/Objects/Tag.php
    @@ -52,6 +52,20 @@ class Tag extends Object
    private $sha;

/**

  • \* comment
    
  • *
    
  • \* @var string
    
  • */
    
  • private $comment;
  • /**
  • \* authorDate
    
  • *
    
  • \* @var string
    
  • */
    
  • private $authorDate;
  • /**

  • Creates a new tag on the repository and returns it
    *

  • @param \GitElephant\Repository $repository repository instance
    @@ -99,12 +113,19 @@ class Tag extends Object

  • @throws \InvalidArgumentException

  • @internal param string $line a single tag line from the git binary
    */

  • public function __construct(Repository $repository, $name)

  • public function __construct(Repository $repository, $name, $sha = null, $authorDate=null, $comment = null)
    {
    $this->repository = $repository;
    $this->name = $name;
    $this->fullRef = 'refs/tags/' . $this->name;

  •    $this->createFromCommand();
    
  •    if (is_null($sha)) {
    
  •        $this->createFromCommand();
    
  •    } else {
    
  •        $this->setSha($sha);
    
  •    }
    
  •    $this->comment = $comment;
    
  •    $this->authorDate = $authorDate;
    

    }

    /**
    @@ -251,4 +272,24 @@ class Tag extends Object
    {
    return $this->sha;
    }
    +

  • /**

  • \* comment getter
    
  • *
    
  • \* @return string
    
  • */
    
  • public function getComment()

  • {

  •    return $this->comment;
    
  • }

  • /**
  • \* authorDate getter
    
  • *
    
  • \* @return string
    
  • */
    
  • public function getAuthorDate()
  • {
  •    return $this->authorDate;
    
  • }
    }
    diff --git a/common/lib/GitElephant/vendor/cypresslab/gitelephant/src/GitElephant/Repository.php b/common/lib/GitElephant/vendor/cypresslab/g
    index 96a473d..659c612 100755
    --- a/common/lib/GitElephant/vendor/cypresslab/gitelephant/src/GitElephant/Repository.php
    +++ b/common/lib/GitElephant/vendor/cypresslab/gitelephant/src/GitElephant/Repository.php
    @@ -40,6 +40,7 @@ use GitElephant\Command\MainCommand;
    use GitElephant\Command\BranchCommand;
    use GitElephant\Command\MergeCommand;
    use GitElephant\Command\ResetCommand;
    +use GitElephant\Command\ForEachRefCommand;
    use GitElephant\Command\RevParseCommand;
    use GitElephant\Command\TagCommand;
    use GitElephant\Command\LogCommand;
    @@ -632,6 +633,31 @@ class Repository
    }

/**

  • \* Gets an array of Tag objects
    
  • *
    
  • \* @param string $count count of the most recent tags to get.
    
  • *
    
  • \* @throws \RuntimeException
    
  • \* @throws \Symfony\Component\Process\Exception\LogicException
    
  • \* @throws \Symfony\Component\Process\Exception\InvalidArgumentException
    
  • \* @throws \Symfony\Component\Process\Exception\RuntimeException
    
  • \* @return array
    
  • */
    
  • public function getRecentTags($count)
  • {
  •    $tags = array();
    
  •    $this->caller->execute(ForEachRefCommand::getInstance()->getRecentTags($count));
    
  •    foreach ($this->caller->getOutputLines() as $tagString) {
    
  •        if ($tagString != '') {
    
  •            $tagComponents = ForEachRefCommand::getInstance()->parseRecentTagString($tagString);
    
  •            $tags[] = new Tag($this, $tagComponents[ForEachRefCommand::COMPONENT_NAME], $tagComponents[ForEachRefCommand::COMPONENT_SHA]
    
  •        }
    
  •    }
    
  •    return $tags;
    
  • }
  • /**
  • Return a tag object
    *
  • @param string $name The tag name
    jschlick@dvm-jschlick1:/usr/local/development-process-master/common/lib/GitElephant/vendor/cypresslab/gitelephant(BGS-1269)$

lists() methods are unconventionally named

The BranchCommand, SubmoduleCommand and TagCommand each have a lists() method, for listing branches, submodules and tags respectively. The name "lists" is unconventional and unintuitive, as it is not an imperative verb phrase. I would imagine this is because "list" is a reserved word in PHP, but "lists" is (in my opinion) a bad compromise.

I have added the methods listBranches(), listSubmodules() and listTags() to these classes, which inherit the logic of the lists() methods. To keep from breaking existing code which may rely on lists(), I have left those functions in place, but they now call their corresponding listX() method and have deprecated notation in their docblocks. I have also updated all uses of lists() methods I could find in the src/ directory.

Add support for -m and --ff-only to git merge

I'd like to add support for the following features of git merge:

  • Commit messages (in case you don't want git's auto-generated messages when doing a 3-way merge)
  • Enforce fast-forward only merging (--ff-only)

$repo->checkout can't specify amongst multiple remote branches. (Solution code included)

Please see the article at:
http://makandracards.com/makandra/14323-git-how-to-check-out-branches-that-exist-on-multiple-remotes

It talks about how to specify amongst remote branches at checkout. Since you also dont support git checkout -b thats included as it's necessary to get teh right syntax.

If you change mainCommand->checkout to look like this:
public function checkout($ref, $createBranch = false, $refSource=null)
{
$this->clearAll();

    $what = $ref;
    if ($ref instanceof Branch) {
        $what = $ref->getName();
    } elseif ($ref instanceof TreeishInterface) {
        $what = $ref->getSha();
    }

    $this->addCommandName(self::GIT_CHECKOUT);
    $this->addCommandArgument('-q');
    if ($createBranch) {
        $this->addCommandArgument('-b');
    }
    $this->addCommandSubject($what);
    if (!is_null($refSource)) {
        $this->addCommandSubject2($refSource);
    }
    return $this->getCommand();
}

and then change repository->checkout to look like:
public function checkout($ref, $createBranch = false, $refSource=null)
{
$this->caller->execute(MainCommand::getInstance()->checkout($ref, $createBranch, $refSource));

    return $this;
}

This will allow a caller to specify a SPECIFIC source for their checkout (if the local branch does not already exist.), as well as specifying that they want a branch created.

Checking out of tag

I'm trying to integrate this library in my Drupal deployment module. I am having problem figuring out how to properly checkout a tag.

What I've tried already:

$release_tag = $repo->getCommit("v.7.26-1.0");
$repo->checkout($release_tag); // checkout a tag

And the error is:

RuntimeException: fatal: ambiguous argument &#039;v.7.26-1.0&#039;: unknown revision or path not in the working tree. Use &#039;--&#039; to separate paths from revisions, like this: &#039;git &lt;command&gt; [&lt;revision&gt;...] -- [&lt;file&gt;...]&#039; in GitElephant\Command\Caller\Caller->execute() (line 103 of /media/Data/www/drupalestate/sites/all/modules/custom/drush_deployment/lib/vendor/cypresslab/gitelephant/src/GitElephant/Command/Caller/Caller.php).

I also tried this as described in the README:

$repo->checkout($this->getCommit("v.7.26-1.0"));

And the error is:

Fatal error: Using $this when not in object context in

Thanks

Consider Improving the API

Hey,

first thanks for your work on this. I've been looking for an object oriented interface to interact with a Git repository, and found this on packagist.org.

Initially, I just wanted to generate a diff between two references (shas). Unfortunately, the repository expects two commit objects as parameters to the diff method which in turn expect $outputLines as their constructor args which I don't have. I also tried using the Diff class directly. That was unfortunately again not possible because the DiffCommand makes some assumptions (like adding dst/src prefixes, ignoring whitespace, and others).

I'd suggest to change the API to something like this:

$repository = new Repository('path/to/repo');
$diff = $repository->diff($baseSha, $headSha, $optionalPathArray);

On a side note, GitHub's library for Git (https://github.com/mojombo/grit) is pretty cool and might give some inspiration.

Add Commit::getRefs() (for Commits returned from Repository::getLog())

First off, thanks for providing this really great library, it really works like a charm. I was looking for a way to create a graph (https://github.com/clue/graph) including all branch labels from my commit history and stumbled upon a couple of issues using this library.

It would be nice if branch labels would be exposed via Commit::getRefs(). Using git log --decorate=full includes all local and remote branches and tag names in the commit log, so that's probably a good start.

Commits from Repository::getLog() superfluously call `git show` and return invalid parents

Sample script:

$repo = new Repository('./');
$repo->getLog();

As expected, the log contains an array of Commits constructed from the log output (Commit::createFromOutputLines()). However, the constructor for each Commit invokes Commit::createFromCommand() which calls git show on the HEAD(!). As such, the each Commit returned in getLog() parses both the parent refs from the actual commit plus the ones from the HEAD.

I have yet to do some more in-depth testing, but the easiest patch seems to be to pass a null $treeish reference to the constructor and then avoid invoking createFromCommand().

error: unknown option `list' when updating/checking out branch

    $repo = new Repository('/home/vagrant/myrepo');
    $repo->updateAllBranches();

error: unknown option `list'
usage: git branch [options] [-r | -a] [--merged | --no-merged]
or: git branch [options] [-l] [-f] []
or: git branch [options] [-r](-d | -D)
or: git branch [options](-m | -M) []

at Caller ->execute ('branch '-v' '--list' '--no-color' '--no-abbrev' '-a' 'master'')
in cypresslab/gitelephant/src/GitElephant/Objects/TreeBranch.php at line 111

I use git 1.7.1. on CentOS 6. I guess this library depends on git 1.8.x? Can't figure out when this option was introduced, in documentation of Git it seems like this option is there forever. Also when doing "git branch --help" it doesnt show anything about the "--list" option.

InvalidArgumentException("the branch string is not valid") thrown if local repo has a detached HEAD

An InvalidArgumentException is thrown by \GitElephant\Objects\Branch::getMatches if a local repo has a detached HEAD. The following output line from git branch -a -v command is the culprit.

* (HEAD detached at 7a02066) 7a020660489a31ed9d0d6a42d5a0f0379334ba82 Merge branch 'PERFORM' into 'master'

But, do we consider a detached head as a branch, maybe called 'HEAD'? Or should we filter this line out first before parsing?

Unit tests don't work (some analysis included), and need to be fixed.

The documentation for running the unit tests is incomplete and incorrect, and the unit tests will not run.

Short version:
the bootstrap file is incorrect for the source code tree structure
Composer --dev installation instructions are wrong
Mockery doesn't get installed with composer using --dev option.

Longer version:
The tests themselves do not include the bootstrap file (which they should), so every time you run phpunit (from the command line, since it's installed), you must specify
--bootstrap ...../GitElephant/vendor/cypresslab/gitelephant/tests/bootstrap.php
This NEEDS to be called out in the documentation, or better yet, do a require_once of the bootstrap file at the top of EVERY unit test in the suite so that it can run without the bootstrap being defined.

Then, the bootstrap.php file does a:
require_once DIR.'/../vendor/autoload.php';
but it lives in the tests dir which is distributed at the SAME LEVEL as src which is above the GitElephant directory. IT APPEARS as though the tests directory is designed to be in the src directory instead of at the same level as it.

OR the bootstrap file needs to be
require_once DIR.'/../../../autoload.php';
instead of
require_once DIR.'/../vendor/autoload.php';

But this then leads to other problems because then class "Mockery" isn't found.
You will say that one should do
Composer --dev install BUT, given the instructions on http://gitelephant.cypresslab.net/gitelephant
it's really
cd xxx/GitElephant/composer.phar
php composer.phar --dev install

which states
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Nothing to install or update
Generating autoload files

then a grep of the entire tree for "Mockery" STILL doesn't find that class.

Bottom line: phpunit on any unit test fails. And thats bad.

When I do a phpunit on a test, it's clear that the bootstrap is missing when you look at this error:
jschlick@dvm-jschlick1:/usr/local/development-process-master/common/lib/GitElephant/vendor/cypresslab/gitelephant/tests/GitElephant(BGS-1269)$ phpunit GitBinaryTest.php
PHP Fatal error: Class 'GitElephant\TestCase' not found in /usr/local/development-process-master/common/lib/GitElephant/vendor/cypresslab/gitelephant/tests/GitElephant/GitBinaryTest.php on line 25
PHP Stack trace:
PHP 1. {main}() /usr/bin/phpunit:0
PHP 2. PHPUnit_TextUI_Command::main() /usr/bin/phpunit:584
PHP 3. PHPUnit_TextUI_Command->run() phar:///usr/bin/phpunit/phpunit/TextUI/Command.php:132
PHP 4. PHPUnit_Runner_BaseTestRunner->getTest() phar:///usr/bin/phpunit/phpunit/TextUI/Command.php:153
PHP 5. PHPUnit_Runner_BaseTestRunner->loadSuiteClass() phar:///usr/bin/phpunit/phpunit/Runner/BaseTestRunner.php:105
PHP 6. PHPUnit_Runner_StandardTestSuiteLoader->load() phar:///usr/bin/phpunit/phpunit/Runner/BaseTestRunner.php:162
PHP 7. PHPUnit_Util_Fileloader::checkAndLoad() phar:///usr/bin/phpunit/phpunit/Runner/StandardTestSuiteLoader.php:78
PHP 8. PHPUnit_Util_Fileloader::load() phar:///usr/bin/phpunit/phpunit/Util/Fileloader.php:77
PHP 9. include_once() phar:///usr/bin/phpunit/phpunit/Util/Fileloader.php:93
jschlick@dvm-jschlick1:/usr/local/development-process-master/common/lib/GitElephant/vendor/cypresslab/gitelephant/tests/GitElephant(BGS-1269)$

When I run this WITH the bootstrap in place, it tells me all about the directory structuire being screwed up.
phpunit --bootstrap /usr/local/development-process-master/common/lib/GitElephant/vendor/cypresslab/gitelephant/tests/bootstrap.php GitBinaryTest.php
PHP Warning: require_once(/usr/local/development-process-master/common/lib/GitElephant/vendor/cypresslab/gitelephant/tests/../vendor/autoload.php): failed to open stream: No such file or directory in /usr/local/development-process-master/common/lib/GitElephant/vendor/cypresslab/gitelephant/tests/bootstrap.php on line 14
PHP Stack trace:
PHP 1. {main}() /usr/bin/phpunit:0
PHP 2. PHPUnit_TextUI_Command::main() /usr/bin/phpunit:584
PHP 3. PHPUnit_TextUI_Command->run() phar:///usr/bin/phpunit/phpunit/TextUI/Command.php:132
PHP 4. PHPUnit_TextUI_Command->handleArguments() phar:///usr/bin/phpunit/phpunit/TextUI/Command.php:141
PHP 5. PHPUnit_TextUI_Command->handleBootstrap() phar:///usr/bin/phpunit/phpunit/TextUI/Command.php:584
PHP 6. PHPUnit_Util_Fileloader::checkAndLoad() phar:///usr/bin/phpunit/phpunit/TextUI/Command.php:806
PHP 7. PHPUnit_Util_Fileloader::load() phar:///usr/bin/phpunit/phpunit/Util/Fileloader.php:77
PHP 8. include_once() phar:///usr/bin/phpunit/phpunit/Util/Fileloader.php:93
PHP Fatal error: require_once(): Failed opening required '/usr/local/development-process-master/common/lib/GitElephant/vendor/cypresslab/gitelephant/tests/../vendor/autoload.php' (include_path='.:/usr/share/php:/usr/share/pear') in /usr/local/development-process-master/common/lib/GitElephant/vendor/cypresslab/gitelephant/tests/bootstrap.php on line 14
PHP Stack trace:
PHP 1. {main}() /usr/bin/phpunit:0
PHP 2. PHPUnit_TextUI_Command::main() /usr/bin/phpunit:584
PHP 3. PHPUnit_TextUI_Command->run() phar:///usr/bin/phpunit/phpunit/TextUI/Command.php:132
PHP 4. PHPUnit_TextUI_Command->handleArguments() phar:///usr/bin/phpunit/phpunit/TextUI/Command.php:141
PHP 5. PHPUnit_TextUI_Command->handleBootstrap() phar:///usr/bin/phpunit/phpunit/TextUI/Command.php:584
PHP 6. PHPUnit_Util_Fileloader::checkAndLoad() phar:///usr/bin/phpunit/phpunit/TextUI/Command.php:806
PHP 7. PHPUnit_Util_Fileloader::load() phar:///usr/bin/phpunit/phpunit/Util/Fileloader.php:77
PHP 8. include_once() phar:///usr/bin/phpunit/phpunit/Util/Fileloader.php:93
jschlick@dvm-jschlick1:/usr/local/development-process-master/common/lib/GitElephant/vendor/cypresslab/gitelephant/tests/GitElephant(BGS-1269)$

Then, when I FIX bootstrap.php, by doing the following:
//require_once DIR.'/../vendor/autoload.php';
require_once DIR.'/../../../autoload.php';
and run it with the bootstrap option, it complains that the Mockery class is missing:

jschlick@dvm-jschlick1:/usr/local/development-process-master/common/lib/GitElephant/vendor/cypresslab/gitelephant/tests/GitElephant(BGS-1269)$ phpunit --bootstrap /usr/local/development-process-master/common/lib/GitElephant/vendor/cypresslab/gitelephant/tests/bootstrap.php GitBinaryTest.php
git version 1.7.9.5
PHPUnit 4.1.0 by Sebastian Bergmann.

PHP Fatal error: Class 'Mockery' not found in /usr/local/development-process-master/common/lib/GitElephant/vendor/cypresslab/gitelephant/tests/GitElephant/TestCase.php on line 118
PHP Stack trace:
PHP 1. {main}() /usr/bin/phpunit:0
PHP 2. PHPUnit_TextUI_Command::main() /usr/bin/phpunit:584
PHP 3. PHPUnit_TextUI_Command->run() phar:///usr/bin/phpunit/phpunit/TextUI/Command.php:132
PHP 4. PHPUnit_TextUI_TestRunner->doRun() phar:///usr/bin/phpunit/phpunit/TextUI/Command.php:179
PHP 5. PHPUnit_Framework_TestSuite->run() phar:///usr/bin/phpunit/phpunit/TextUI/TestRunner.php:426
PHP 6. PHPUnit_Framework_TestCase->run() phar:///usr/bin/phpunit/phpunit/Framework/TestSuite.php:675
PHP 7. PHPUnit_Framework_TestResult->run() phar:///usr/bin/phpunit/phpunit/Framework/TestCase.php:753
PHP 8. PHPUnit_Framework_TestCase->runBare() phar:///usr/bin/phpunit/phpunit/Framework/TestResult.php:686
PHP 9. GitElephant\TestCase->tearDown() phar:///usr/bin/phpunit/phpunit/Framework/TestCase.php:842
jschlick@dvm-jschlick1:/usr/local/development-process-master/common/lib/GitElephant/vendor/cypresslab/gitelephant/tests/GitElephant(BGS-1269)$

$repo->getTag is WAY too slow. (Solution code included)

In a repository that has MANY tags, getTag relies on GetTags which spawns off an exec for each tag to generate the object, then it looks at the getName of the object.

If it were implemented like getTagOrBranch, it would be WAY WAY faster.

Here is the code:
public function getTag($name)
{
$tagFinderOutput = $this->caller->execute(TagCommand::getInstance()->lists())->getOutputLines(true);
foreach ($tagFinderOutput as $line) {
if ($line === $name) {
return new Tag($this, $name);
}
}

    return null;
}

Changing repository path

We inject an instance of GitElephant\Repository into a service, but I need to be able to set the repository path afterwards (e.g., a command line option to override the default repository path).

I can submit a PR but want to know which you prefer:

  • make $path a protected property, or
  • add a public setRepositoryPath() method

Wrong license header

The current license header looks like this.

/**
* GitElephant - An abstraction layer for git written in PHP
* Copyright (C) 2013 Matteo Giachino
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see [http://www.gnu.org/licenses/].
*/

But according to the https://www.gnu.org/licenses/gpl-howto.html you should. "When using the Lesser GPL, insert the word “Lesser” before “General” in all three places. When using the GNU AGPL, insert the word “Affero” before “General” in all three places."

So it should be.

/**
* GitElephant - An abstraction layer for git written in PHP
* Copyright (C) 2013 Matteo Giachino
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see [http://www.gnu.org/licenses/].
*/

Supports for remote commands

Hi,

It seems there is ATM no supports for remote commands

git remote -v
git remote add [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>]
git remote rename <old> <new>
git remote rm <name>
git remote set-head <name> (-a | -d | <branch>)
git remote [-v | --verbose] show [-n] <name>
git remote prune [-n | --dry-run] <name>
git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]
git remote set-branches [--add] <name> <branch>...
git remote set-url [--push] <name> <newurl> [<oldurl>]
git remote set-url --add <name> <newurl>
git remote set-url --delete <name> <url>

I guess supporting all theses methods are not necessary for a first time, i can make a PR to add this on API:

$repository->getRemotes();
$repository->getRemoteInfo($remote);
$repository->addRemote($name, $url);
$repository->removeRemote($name);

git elephant gush integration

sometimes i think that having an abstraction in gush could be used standalone

i mean gush uses adapters to do macro hub operations, like hub

i think it is possible perhaps to create an adapter for gitelephant, so a gush-gitelephant-adapter in such a way that the adapter and issue tracker interface implementations looks like it is interacting with github but it is stored or hold in our servers or locally

this would be of great interest to gain a good abstraction between gush and gitelephant for the profit of projects

cc/ @sstok

PEAR

Hi

Is it possible to install this through PEAR ?

Global configs and options don't work because they are being set prior to a clearAll()

I found a bug in the code I wrote a while back for global options and configs. In isolated testing everything works, but when I actually tried to implement a feature utilizing a global option ("git checkout --work-tree"), I discovered that the global option never makes it to the final generated command because the clearAll() call erases the globals.

Now that I'm thinking straight, BaseCommand::clearAll() should not reset any global options, since these are meant to apply to all git commands globally. I will be submitting a pull request soon to make this change.

clone to existing folder

I need to clone into a folder I make in advance. However, GitElephant seems to create a subfolder for the repository, no matter what I try.

Calling Repository::getBranches() will execute "git branch" command the same number of times as the number of branches + 1

I added the following to GitElephant\Command\Caller\Caller::execute method to see how GitElephant calls the underlying Git binary:

    print $cmd.'<br />';

And found out that whenever GitElephant\Repository::getBranches is called, the command git branch will be executed the same number of times as the number of local branches plus one. So if I have four branches, the command will be executed five times. Not so optimized.

Diving into the code, I found out that the following method calls happen when calling getBranches:

  1. BranchCommand::listBranches is executed. This is the first git branch command execution.
  2. For each output line, Branch::createFromOutputLine is called.
  3. In Branch::createFromOutputLine, constructor of Branch class is called.
  4. The constructor calls Branch::createFromCommand method.
  5. Inside Branch::createFromCommand method, BranchCommand::listBranches is executed. This will be repeated for each output line processed in step 2 above.
  6. Branch::createFromCommand method will call Branch::parseOutputLine. Returning to the constructor and then returning to Branch::createFromOutputLine method, Branch::parseOutputLine is called again using output line passed from Repository::getBranches method.

In step 6, the parseOutputLine method is called twice, which can be reduced to one if we can make the Branch::createFromOutputLine method call the Branch class constructor without calling Branch::createFromCommand because the output line needed to be parsed is already been provided as a parameter of Branch::createFromOutputLine.

Using version 1.1.0 via composer.

Please comment.

Can't force the deletion of a git branch. (Solution code included)

Git allows for the FORCE deletion of a branch via the -D option for branches that aren't merged into the current branch. Your solution only currently supports the -d option.

Same comment about unit testing as last time. When you release a version where the unit tests work without any additional shenanigans, I'll happily generate pull requests instead of issues.

In the meantime, here is the simple solution.
in BranchCommand, the delete function becomes:

public function delete($name, $force=false)
{
    $this->clearAll();
    $this->addCommandName(self::BRANCH_COMMAND);
    if ($force) {
       $this->addCommandArgument('-D');
    } else {
       $this->addCommandArgument('-d');
    }
    $this->addCommandSubject($name);

    return $this->getCommand();
}

and in repository.php deleteBranch becomes:
public function deleteBranch($name, $force = false)
{
$this->caller->execute(BranchCommand::getInstance()->delete($name, $force));

    return $this;
}

DiffCommand fails if user has git configured to use an external diff

I have a user that has an external diff tool configured in their .config file:
[diff]
external = extDiff
tool = coderev
[diff "tool.coderev"]
cmd = /work/utils/coderev-master/codediff.py $REMOTE $LOCAL -o ~/remote-home/diff.html
[difftool]
prompt = false

The problem is that when gitElephant uses Git to do a diff - it pops up the external diff tool. When he exits, it somehow sends the wrong exist status back to GitElephant which causes - well, "bad things". (exit status stack dump, etc)

I am not overly familiar with this config file setup. My proposed solution was to add "--no-ext-diff" (which works when you do it on the command line) to the commandline arguments of the diffCommand, but that didn't stop it from popping the diff viewer. So I'm not sure what the best way to disable this is. (And knowing that some parameters need to come before others, I tried the parameter at the end of the arguments as well as at the beginning.) So I have a user that can't run our tools that use gitElephant because of this.

Any thoughts?

proposed (but not working) Diff of DiffCommand.php follows:

diff --git a/common/lib/GitElephant/vendor/cypresslab/gitelephant/src/GitElephant/Command/DiffCommand.php b/common/lib/GitElephant/vendor/cypresslab/gitelephant/src/GitElephant/Command/DiffCommand.php
old mode 100644
new mode 100755
index 306570f..6c9797a
--- a/common/lib/GitElephant/vendor/cypresslab/gitelephant/src/GitElephant/Command/DiffCommand.php
+++ b/common/lib/GitElephant/vendor/cypresslab/gitelephant/src/GitElephant/Command/DiffCommand.php
@@ -29,6 +29,8 @@ use GitElephant\Objects\TreeishInterface;
class DiffCommand extends BaseCommand
{
const DIFF_COMMAND = 'diff';

  • const NO_COLOR_OPTION = '--no-color';
  • const NO_EXTERNAL_DIFF_OPTION = '--no-ext-diff';

/**

  • @return DiffCommand
    @@ -53,10 +55,11 @@ class DiffCommand extends BaseCommand
    $this->clearAll();
    $this->addCommandName(self::DIFF_COMMAND);
    $this->addCommandArgument('--full-index');
  •    $this->addCommandArgument('--no-color');
    
  •    $this->addCommandArgument(self::NO_COLOR_OPTION);
     $this->addCommandArgument('-M');
     $this->addCommandArgument('--dst-prefix=DST/');
     $this->addCommandArgument('--src-prefix=SRC/');
    
  •    $this->addCommandArgument(self::NO_EXTERNAL_DIFF_OPTION);
    
     $subject = '';
    

Invalid branch name

When im using

$branches = $repository->getBranches(false, true);

im getting this error

the branch string is not valid: remotes/origin/HEAD -> origin/master
/vendor/cypresslab/gitelephant/src/GitElephant/Objects/Branch.php:199

    public static function getMatches($branchString)
    {
        $matches = array();
        preg_match('/^\*?\ *?(\S+)\ +(\S{40})\ +(.+)$/', trim($branchString), $matches);
        if (!count($matches)) {
            throw new \InvalidArgumentException(sprintf('the branch string is not valid: %s', $branchString));
        }

preserve raw diff output

When generating a diff object, there is no way to retrieve the raw diff output from git. This can be useful, since lots of tools know how to process (unified) diff strings.

In my use case, I wanted to display a diff in HTML easily/quickly by using https://github.com/rtfpessoa/diff2html

If you like the idea I could make a PR.

Status can't return merge conflict files.(Includes solution code)

I need to be able to see the files in my branch that have a merge conflict.

This involves changing Status/Status.php

Add the following routine:
/**
* merge conflict files
*
* @return Sequence
*/
public function mergeConflict()
{
return $this->filterByType(StatusFile::UPDATED_BUT_UNMERGED, StatusFile::UPDATED_BUT_UNMERGED);
}

and modify protected function filterByType to be defined as
protected function filterByType($type, $typeY=null)

and include
// If they give us a separate typeY then they are picky about the status they want.
if (!is_null($typeY)) {
return new Sequence(array_filter($this->files, function (StatusFile $statusFile) use ($type, $typeY) {
return $typeY === $statusFile->getWorkingTreeStatus() && $type === $statusFile->getIndexStatus();
}));
}

after the first if.

This will allow the status class to return merge conflicts as a sequence.

John.

[Q] About SSH Agent

Hello @matteosister,

I am running into a problem that will probably have an obvious solution.

What I am trying to achieve: Cloning locally a private repository protected by a ssh auth.
The problem: "Permission denied, please try again.", the key pair is not working.
Consideration: Apache/PHP user have access to that key-pair and the crashing command work in a terminal.
Reason: In https://github.com/matteosister/GitElephant/blob/v1.0.14/src/GitElephant/Command/Caller/Caller.php#L109 the clone command is executed using symfony process component. It appear that the Process does not have access to the ssh agent.

Indeed if I replace (for testing purpose)

    $process = new Process($cmd, is_null($cwd) ? $this->repositoryPath : $cwd);

with (starting the ssh-agent):

   $process = new Process('exec ssh-agent bash && ' .$cmd, is_null($cwd) ? $this->repositoryPath : $cwd);

My question:
How do you transmit your agent to the library ?

Thanks :)

Diff mode null when file mode changes.

With this piece of diff, the "diff" mode is not being set in the class (being set to null).

diff --git SRC/.env.example DST/.env.example
old mode 100644
new mode 100755
index 214b4621d8ef55bb450
7448976739ad1c1e35bfd..fbb124eaf8a16072af5509c44218baf24af7c019

full diff

diff --git SRC/.env.example DST/.env.example
old mode 100644
new mode 100755
index 214b4621d8ef55bb4507448976739ad1c1e35bfd..fbb124eaf8a16072af5509c44218baf24af7c019
--- SRC/.env.example
+++ DST/.env.example
@@ -1,5 +1,6 @@
 APP_ENV=local
 APP_DEBUG=true
+APP_URL=http://siteurl.pl
 APP_KEY=SomeRandomString
 DB_HOST=localhost

git version 2.1.4

Support for "git branch --merged" is needed. (solution code included)

Since I can't get your unit tests to run, and without that you refuse to take pull requests, here is what it takes to implement "git branch --merged" (which I need in my toolset to determine if a particular branch is merged into another one or not.)

in BranchCommand.php the lists function line adds a merged parameter:
move the if all test above the if not simple
then below the if not simple change the two --no-color --no-abbrev lines to read:
and it becomes:
public function lists($all = false, $simple = false, $merged = false)
{
$this->clearAll();
$this->addCommandName(self::BRANCH_COMMAND);
// -a comes before --merged or it fails.
if ($all) {
$this->addCommandArgument('-a');
}
if (!$simple) {
$this->addCommandArgument('-v');
}
//no-abbrev must come before the merged option.
$this->addCommandArgument('--no-abbrev');
// Merged doesn't like --no-color etc, but if not, we want that option.
if ($merged) {
$this->addCommandArgument('--merged');
} else {
$this->addCommandArgument('--no-color');
}

    return $this->getCommand();
}

Then in Repository.php getBranches function line becomes:
public function getBranches($namesOnly = false, $all = false, $merged = false)

and the namesonly caller->execute line becomes:
$outputLines = $this->caller->execute(BranchCommand::getInstance()->lists($all, true, $merged))->getOutputLines(

and the second one (not namesonly) becomes:
$outputLines = $this->caller->execute(BranchCommand::getInstance()->lists($all, false, $merged))->getOutputLines(true);

Pretty simple, it only touches 2 files to make this happen.

$repo->merge can't merge against a tag. (Solution code included)

In general git can merge against a branch or a tag as long as it can translate that to a commit sha.
The current codebase insists that what it passed to merge be a branch.
I NEED to be able to merge against tags for the release process that we have.

So, the following code will allow you to pass a branch OR a tag to repository->merge
First remove the branch specifier from merge in repository.php:
public function merge($branch)

and then recode MergeCommand.php merge to look like:
public function merge($with)
{
$this->clearAll();
$this->addCommandName(static::MERGE_COMMAND);
// $with may not always be a branch. It can be a tag.
if (method_exists($with, 'setFullRef')) {
$ref = $with->getFullRef();
} else {
$ref = $with->getName();
}
$this->addCommandSubject($ref);

    return $this->getCommand();
}

This removes the Branch specifier from the calling parameters, and also checks to make sure that the getFullRef is there (when it's a branch, if not it assumes it's a tag and uses getName)

This is not a perfect implementation as we should also be able to pass in a commit, but since I haven't had to deal with that yet, I've done what I need, and this is healthier than it was.

remove behat from composer.json

hi,

would it be possible to remove behat from dependencies in non development versions? it causes me problems to install in first place and furthermore i think its not needed in version intended for production use. thanks

Is possible to get history of one file ?

Hi !

I can't find how to get the history of a single file with GitElephant.
I want be able to get all the history of a file and his diff from a path.

If a good soul could give me a little help :)

Thanks a lot.

Windows: should not use PHP_EOL to split Git binary output lines

Dear sir,

On Windows machine, Git binary outputs lines using the *nix termination character "\n", not the usual "\r\n". Thus, PHP_EOL should not be used in the class method & line

// GitElephant\Command\Caller::execute() line 121:
$values = array_map('rtrim', explode(PHP_EOL, $process->getOutput()));

It should use "\n" instead:

$values = array_map('rtrim', explode("\n", $process->getOutput()));

Thank you.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.