guyzmo / git-repo

Git-Repo: CLI utility to manage git services from your workspace
https://webchat.freenode.net/?channels=#git-repo
Other
842 stars 85 forks source link

Listing forks #176

Open stelcheck opened 7 years ago

stelcheck commented 7 years ago

It would be very nice if we could:

  1. List the forks of a given upstream
  2. Maybe allow syncing the list of forks with the list of local remotes?

One challenge I would see is to figure out which repository is the blessed one in the case one works with a fork of their own. But I personally love doing git fetch --all to pull the code of everyone I work with, and I would love it if there was a way for me to figure out which forks to add from the command line.

guyzmo commented 7 years ago

Hi! Sorry for the delay to respond.

Well, it looks like there's an API for that:

I'd say we could design a subcommand forks that would go like:

% git hub guyzmo/git-repo forks list
abutbul/git-repo
akabrainz/git-repo
AmandaCameron/git-repo
benzaita/git-repo
bestwpw/git-repo
calvimor/git-repo
containerz/git-repo
Crazybus/git-repo
dmerejkowsky/git-repo
dragon788/git-repo
fa7ad/git-repo
frankrousseau/git-repo
git-repo-test/git-repo
gpocentek/git-repo
graingert/git-repo
GuillaumeSeren/git-repo
guyhughes/git-repo
ivoleitao/git-repo
jayvdb/git-repo
johnbickmore/git-repo
king6cong/git-repo
kounoike/git-repo
lafrenierejm/git-repo
LinuxBai/git-repo
masaeedu/git-repo
math4youbyusgroupillinois/git-repo
montazze/git-repo
ngksternhagen/git-repo
nkabir/git-repo
octaltree/git-repo
peterazmanov/git-repo
pombredanne/git-repo-2
pyhedgehog/git-repo
Razer6/git-repo
rlafuente/git-repo
rnestler/git-repo
shaunstanislaus/git-repo
sukiletxe/git-repo
tdy/git-repo
tengjingdong/git-repo
waffle-iron/git-repo
westes/git-repo
harendranathvegi9/git-repo
kleopatra999/git-repo-1
Xenopathic/git-repo
zkanda/git-repo

then you could fetch one fork using:

% git hub forks fetch pombredanne/git-repo-2
Fetching pombredanne/git-repo-2…
Fetched pombredanne/git-repo-2 as remote `github/pombredanne`

(or specify the remote name as last argument:

% git <target> forks fetch <slug> <remote>

and for your own pleasure, we could add:

% git hub forks fetch --all

About listing, we could use the same algorithm as for git <target> list to pack the list in columns, so we could have forks ls with columns and forks ls -l for the above listing… And we could even have fun and do a tree listing of it (using like ls --tree?):

% git hub forks ls --tree
guyzmo/git-repo
├─ abutbul/git-repo
├─ akabrainz/git-repo
├─ AmandaCameron/git-repo
…
├─ tengjingdong/git-repo
├─ waffle-iron/git-repo
├┬ westes/git-repo
│├─ harendranathvegi9/git-repo
│└─ kleopatra999/git-repo-1
├─ Xenopathic/git-repo
└─ zkanda/git-repo

The code should be pretty close to the existing request list and request fetch features, so it shouldn't be hard to write.

Sadly, I'm quite snowed currently so I have no idea when will be the next time I'll have time to sit and give time to this project to implement new features. But I'll be happy to help anybody ready to write the feature!

stelcheck commented 7 years ago

That would be perfect. I don't have much time myself right now, but if you can point me at where to look to get started, I'd be happy to give it a shot.

guyzmo commented 7 years ago

to add the new feature in git-repo, here's the gist:

Add the CLI option

first you add in the docstring for docopt the commands you want to create, something like:

    {self} [--path=<path>] [-v...] <target> forks [list|ls] <namespace>/<repo> [--long] [--tree]
    {self} [--path=<path>] [-v...] <target> forks fetch <namespace>/<repo> [<name>]

you might want to document it a bit more at the end of the docstring to give some context to the parameters. A bit like that, and just below it.

Implement the actions handler

At the end of the part where you register actions, you create a function pretty much like the list action.

    @register_action('forks', 'ls') # there you get the "actions" as defined above, in order
    @register_action('forks', 'list')
    def do_list(self):
        print_iter(self.get_service(False).forks_list(self.long, self.tree)) # there you get other `--` parameters within self, thanks to some automagic helper built in git-repo's CLI dispatcher
        return 0

Add a placeholder in the RepositoryService class

The service class is the fallback for all services that don't implement a feature, it's behaving like an abstract class all services shall implement. Here you've got to add do_list at (this position)[https://github.com/guyzmo/git-repo/blob/devel/git_repo/services/service.py#L587] that documents the feature and throws a NotImplementedError. That way, we can implement the error gradually, and have errors for the non-implemented services.

Implement the feature for each service.

That's where the real deal is. The implementation of the API of the service so it offers a single CLI API. RepositoryService is a common python API, and here the game is to make the service's remote API follow a single homogeneous python API. It can be tough at times.

For the list operation, it's quite straightforward, and very close to the list operation. The implementation of such method is to yield the output, with:

For the fetch operation, the request_fetch method shows how you can use gitpython to fetch a ref from a remote. And how you'll actually get that remote information, you can build it by using self.format_path(<repository>, <namespace>, rw=<if True return an SSH URI, HTTPS otherwise>).

Implement tests

Testing thoroughly the code (within reason) is important, and to do so I've implemented the following process. When testing, there's two logical parts in the code, split by the homogeneous python API I talked about above.

Test the CLI side

in the test_main module you'll want to test all the possible arguments you throw at the CLI and make sure it behaves nicely. For the listing, it'll be very much alike test_request_list. There you'll see an example on how I create a temporary repository because I list remotes from within the repository. That's not necessary for test_forks_list.

But you'll need to implement a method like main_request_list which emulates the CLI call of the action. Then we need to implement the mockup of the service part, which is adding a member with the arguments, and a method alike the one in repo.py over here. Nothing fancy, just so you test that the inputs to RepositoryService matches the CLI inputs.

Test the RepositoryService side and API calls

Finally, the second part of testing. There you implement an action_ method that contains the with ... use_cassette call the same as all the others (like action_request_list). The cassette is a betamax feature, that will record all API calls the first time you run the test, and replay those calls every other time. You'll get a new JSON file after running the tests you can add and push to the PR.

The list test is pretty much like action_request_list, which will enable you to test the output of the RepositoryService you wrote against lists of tuples you want to check against, as it's done for github over here.

Final words

You can have some hicups with the tests, even though it's now better than it once was. Though, I hope my "new feature" tutorial was thorough enough to help get you started on that feature ☺ I hope that at some point I'll have the leisure of doing some diagrams to show how all is connected!