Skip to content

Commit 90ffda7

Browse files
committed
Merge branch 'devel' ; Version bump to 1.6.0!
* Adding gist feature support * Adding request for merges feature support * Applied PR #2 and #3 (token and documentation updates) * Applied fix for issues #7 and #8, thanks to patch applied on upstream * Improved testing framework (removed dep to unittest, and made it to work for travis and locally in a smoother way) * Version pinning for important deps to avoid uncontrolled breakage in the future
2 parents 4cb0b86 + be92352 commit 90ffda7

File tree

44 files changed

+3576
-93
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+3576
-93
lines changed

README.md

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,55 @@ and of course, you can delete it using:
4848

4949
% git bb delete guyzmo/git-repo
5050

51+
Once you're all set with your repository, you can check requests to merge
52+
(aka Pull Requests on github) using the `request` command:
53+
54+
% git hub request guyzmo/git-repo list
55+
List of open requests to merge:
56+
id title URL
57+
2 prefer gitrepo.<target>.token > privatekey, docs https://api.github.com/repos/guyzmo/git-repo/issues/2
58+
59+
And fetch it locally to check and/or amend it before merging:
60+
61+
% git hub request guyzmo/git-repo fetch 2
62+
5163
Finally, you can open the repository's page, using the `open` command:
5264

5365
% git lab open guyzmo/git-repo
66+
Successfully fetched branch `2` of `guyzmo/git-repo` into `request-2`!
67+
68+
Finally, another extra feature you can play with is the gist handling:
69+
70+
% git hub gist list
71+
id title
72+
https://gist.github.com/4a0dd9177524b2b125e9166640666737 This is a test gist
73+
74+
Then you can list files within it:
75+
76+
% git hub gist list a7ce4fddba7744ddf335
77+
language size name
78+
Python 1048 unicode_combined.py
79+
% git hub -v gist list https://gist.github.com/4a0dd9177524b2b125e9166640666737
80+
language size name
81+
Markdown 16 README.md
82+
Text 14 LICENSE
83+
reStructuredText 17 README.rst
84+
85+
to output it locally, you can use the fetch command (and specify the file if there's more than one):
86+
87+
% git hub gist fetch https://gist.github.com/a7ce4fddba7744ddf335 > mygist.py
88+
% git hub gist fetch 4a0dd9177524b2b125e9166640666737 LICENSE > LICENSE_from_gist
89+
90+
but for more thorough modifications or consulting, you can as well clone it:
91+
92+
% git hub gist clone 4a0dd9177524b2b125e9166640666737
93+
Pulling from github |████████████████████████████████|
94+
Successfully cloned `4a0dd9177524b2b125e9166640666737` into `./4a0dd9177524b2b125e9166640666737`!
95+
96+
And when you're done you just get rid of it:
97+
98+
% git hub gist -f delete 4a0dd9177524b2b125e9166640666737
99+
Successfully deleted gist!
54100

55101
> *Nota Bene*: Thanks to `git` CLI flexibility, by installing `git-repo` you directly
56102
> have acces to the tool using `git-repo hub …` or `git repo hub …`. For the
@@ -77,13 +123,13 @@ To configure `git-repo` you need to tweak your `~/.gitconfig`. For each service
77123
you've got an account on, you have to make a section in the gitconfig:
78124

79125
[gitrepo "gitlab"]
80-
private_token = YourVerySecretKey
126+
token = YourVerySecretKey
81127

82128
[gitrepo "github"]
83-
private_token = YourOtherVerySecretKey
129+
token = YourOtherVerySecretKey
84130

85131
[gitrepo "bitbucket"]
86-
private_token = username:password
132+
token = username:password
87133

88134
Here, we're setting the basics: just the private token. You'll notice that for bitbucket
89135
the private token is your username and password seperated by a column. That's because
@@ -94,7 +140,7 @@ You also have the ability to set up an alias:
94140

95141
[gitrepo "bitbucket"]
96142
alias = bit
97-
private_token = username:password
143+
token = username:password
98144

99145
that will change the command you use for a name you'll prefer to handle actions
100146
for the service you use:
@@ -105,7 +151,7 @@ Also, you can setup your own gitlab self-hosted server, using that configuration
105151

106152
[gitrepo "myprecious"]
107153
type = gitlab
108-
private_token = YourSuperPrivateKey
154+
token = YourSuperPrivateKey
109155
fqdn = gitlab.example.org
110156

111157
Finally, to make it really cool, you can make a few aliases in your gitconfig:
@@ -173,13 +219,20 @@ To use your own credentials, you can setup the following environment variables:
173219
* [x] add regression tests (and actually find a smart way to implement them…)
174220
* [x] add travis build
175221
* [ ] add support for handling gists
176-
* [ ] add support for handling pull requests
177-
* [ ] list them
178-
* [ ] fetch them as local branches
222+
* [x] github support
223+
* [ ] gitlab support
224+
* [ ] bitbucket support
225+
* [ ] add support for handling pull requests
226+
* [x] list them
227+
* [x] fetch them as local branches
228+
* [x] github support
229+
* [ ] gitlab support
230+
* [ ] bitbucket support
179231
* [ ] add OAuth support for bitbucket
180232
* [ ] show a nice progress bar, while it's fetching
181233
* partly implemented: the issue looks like that gitpython expects output from git
182234
on stderr, whereas it's outputing on stdout.
235+
* [ ] do what's needed to make a nice documentation (if possible in markdown !@#$)
183236
* for more features, write an issue or, even better, a PR!
184237

185238
### License

buildout.cfg

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ develop = .
77
eggs-directory = ${buildout:directory}/var/eggs
88
develop-eggs-directory = ${buildout:directory}/var/develop-eggs
99
parts-directory = ${buildout:directory}/var/parts
10-
#develop-dir = ${buildout:directory}/var/clone/
10+
develop-dir = ${buildout:directory}/var/clone/
11+
extensions=gp.vcsdevelop
12+
vcs-extend-develop=git+https://github.com/gitpython-developers/GitPython#egg=GitPython
1113

1214
[git_repo]
1315
recipe = zc.recipe.egg
@@ -22,7 +24,8 @@ eggs = pytest
2224
pytest-cov
2325
pytest-xdist
2426
pytest-sugar
25-
pytest-capturelog
27+
pytest-catchlog
28+
pytest-datadir-ng
2629
testfixtures
2730
mock
2831
betamax==0.5.1

git_repo/repo.py

Lines changed: 95 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
{self} [--path=<path>] [-v -v...] <target> create <user>/<repo> [--add]
99
{self} [--path=<path>] [-v -v...] <target> delete <user>/<repo> [-f]
1010
{self} [--path=<path>] [-v -v...] <target> open [<user>/<repo>]
11+
{self} [--path=<path>] [-v -v...] <target> request [<user>/<repo>] (list|ls)
12+
{self} [--path=<path>] [-v -v...] <target> request [<user>/<repo>] fetch <request>
13+
{self} [--path=<path>] [-v -v...] <target> gist (list|ls) [<gist>]
14+
{self} [--path=<path>] [-v -v...] <target> gist clone <gist>
15+
{self} [--path=<path>] [-v -v...] <target> gist fetch <gist> [<gist_file>]
16+
{self} [--path=<path>] [-v -v...] <target> gist create [--secret] <description> [<gist_path> <gist_path>...]
17+
{self} [--path=<path>] [-v -v...] <target> gist delete <gist> [-f]
1118
{self} --help
1219
1320
Tool for managing remote repository services.
@@ -18,15 +25,13 @@
1825
fork Fork (and clone) the repository from the service
1926
create Make this repository a new remote on the service
2027
delete Delete the remote repository
28+
gist Manages gist files
29+
request Handles requests for merge
2130
open Open the given or current repository in a browser
2231
2332
Options:
2433
<user>/<repo> Repository to work with
25-
<branch> Branch to pull (when cloning) [default: master]
2634
-p,--path=<path> Path to work on [default: .]
27-
-f,--force Do not ask for confirmation
28-
--clone Clone locally after fork
29-
--add Add to local repository after creation
3035
-v,--verbose Makes it more chatty (repeat twice to see git commands)
3136
-h,--help Shows this message
3237
@@ -35,22 +40,41 @@
3540
-t,--tracking <branch> Makes this remote tracking for the current branch
3641
-a,--alone Does not add the remote to the 'all' remote
3742
43+
Options for fork and clone:
44+
<branch> Branch to pull (when cloning) [default: master]
45+
--clone Clone locally after fork
46+
47+
Options for create:
48+
--add Add to local repository after creation
49+
50+
Options for delete:
51+
-f,--force Do not ask for confirmation
52+
53+
Options for gist:
54+
<gist> Identifier of the gist to fetch
55+
<gist_file> Name of the file to fetch
56+
<gist_path> Name of the file or directory to use for a new gist.
57+
If path is a directory, all files directly within it
58+
will be pushed. If a list of path is given, all files
59+
from them will be pushed.
60+
--secret Do not publicize gist when pushing
61+
3862
Configuration options:
3963
alias Name to use for the git remote
4064
url URL of the repository
41-
private-key Private key to use for connecting to the service
65+
fqdn URL of the repository
4266
type Name of the service to use (github, gitlab, bitbucket)
4367
4468
Configuration example:
4569
4670
[gitrepo "gitlab"]
47-
private-key = YourSecretKey
71+
token = yourapitoken
4872
alias = lab
4973
5074
[gitrepo "personal"]
5175
type = gitlab
52-
private-key = YourSecretKey
53-
url = http://custom.org
76+
token = yourapitoken
77+
fqdn = custom.org
5478
5579
{self} version {version}, Copyright ⓒ2016 Bernard `Guyzmo` Pratz
5680
{self} comes with ABSOLUTELY NO WARRANTY; for more informations
@@ -83,6 +107,19 @@
83107
from git import Repo, Git
84108
from git.exc import InvalidGitRepositoryError, NoSuchPathError
85109

110+
def confirm(what, where):
111+
ans = input('Are you sure you want to delete the '
112+
'{} {} from the service?\n[yN]> '.format(what, where))
113+
if 'y' in ans:
114+
ans = input('Are you really sure? there\'s no coming back!\n'
115+
'[type \'burn!\' to proceed]> ')
116+
if 'burn!' != ans:
117+
return False
118+
else:
119+
return False
120+
return True
121+
122+
86123
def main(args):
87124
try:
88125
if args['--verbose'] >= 5: # pragma: no cover
@@ -116,7 +153,7 @@ def main(args):
116153
if 'GIT_WORK_TREE' in os.environ.keys() or 'GIT_DIR' in os.environ.keys(): #pragma: no cover
117154
del os.environ['GIT_WORK_TREE']
118155

119-
if '/' in args['<user>/<repo>']:
156+
if args['<user>/<repo>'] and '/' in args['<user>/<repo>']:
120157
if len(args['<user>/<repo>'].split('/')) > 2:
121158
raise ArgumentError('Too many slashes.'
122159
'Format of the parameter is <user>/<repo> or <repo>.')
@@ -125,7 +162,7 @@ def main(args):
125162
user = None
126163
repo = args['<user>/<repo>']
127164

128-
if args['create'] or args['add'] or args['delete'] or args['open']:
165+
if args['create'] and not args['gist'] or args['add'] or args['delete'] and not args['gist'] or args['open'] or args['request']:
129166
# Try to resolve existing repository path
130167
try:
131168
try:
@@ -156,14 +193,7 @@ def main(args):
156193

157194
elif args['delete']:
158195
if not args['--force']: # pragma: no cover
159-
ans = input('Are you sure you want to delete the repository '
160-
'{} from the server?\n[yN]> '.format(args['<user>/<repo>']))
161-
if 'y' in ans:
162-
ans = input('Are you really sure? there\'s no coming back!\n'
163-
'[type \'burn!\' to proceed]> ')
164-
if 'burn!' != ans:
165-
return 0
166-
else:
196+
if not confirm('repository', args['<user>/<repo>']):
167197
return 0
168198

169199
if user:
@@ -175,6 +205,20 @@ def main(args):
175205
service.name)
176206
)
177207

208+
elif args['request']:
209+
if args['list'] or args['ls']:
210+
log.info('List of open requests to merge:')
211+
log.info(" {}\t{}\t{}".format('id', 'title'.ljust(60), 'URL'))
212+
for pr in service.request_list(user, repo):
213+
print("{}\t{}\t{}".format(pr[0].rjust(3), pr[1][:60].ljust(60), pr[2]))
214+
elif args['fetch'] and args['<request>']:
215+
new_branch = service.request_fetch(user, repo, args['<request>'])
216+
log.info('Successfully fetched request id `{}` of `{}` into `{}`!'.format(
217+
args['<request>'],
218+
args['<user>/<repo>'],
219+
new_branch)
220+
)
221+
178222
elif args['open']:
179223
RepositoryService.get_service(None, args['<target>']).open(user, repo)
180224

@@ -196,7 +240,7 @@ def main(args):
196240
raise FileExistsError('Cannot clone repository, '
197241
'a folder named {} already exists!'.format(repo))
198242

199-
elif args['clone']:
243+
elif args['clone'] and not args['gist']:
200244
repo_path = os.path.join(args['--path'], repo)
201245
repository = Repo.init(repo_path)
202246
service = RepositoryService.get_service(repository, args['<target>'])
@@ -207,6 +251,38 @@ def main(args):
207251
)
208252
return 0
209253

254+
elif args['gist']:
255+
service = RepositoryService.get_service(None, args['<target>'])
256+
service.connect()
257+
if args['list'] or args['ls']:
258+
if args['<gist>']:
259+
log.info("{:15}\t{:>7}\t{}".format('language', 'size', 'name'))
260+
for gist_file in service.gist_list(args['<gist>']):
261+
print("{:15}\t{:7}\t{}".format(*gist_file))
262+
else:
263+
log.info("{:56}\t{}".format('id', 'title'.ljust(60)))
264+
for gist in service.gist_list():
265+
print( "{:56}\t{}".format(gist[0], gist[1]))
266+
elif args['fetch']:
267+
# send gist to stdout, not using log.info on purpose here!
268+
print(service.gist_fetch(args['<gist>'], args['<gist_file>']))
269+
elif args['clone']:
270+
repo_path = os.path.join(args['--path'], args['<gist>'].split('/')[-1])
271+
service.repository = Repo.init(repo_path)
272+
service.gist_clone(args['<gist>'])
273+
log.info('Successfully cloned `{}` into `{}`!'.format( args['<gist>'], repo_path))
274+
elif args['create']:
275+
url = service.gist_create(args['<gist_path>'], args['<description>'], args['--secret'])
276+
log.info('Successfully created gist `{}`!'.format(url))
277+
elif args['delete']:
278+
if not args['--force']: # pragma: no cover
279+
if not confirm('gist', args['<gist>']):
280+
return 0
281+
282+
service.gist_delete(args['<gist>'])
283+
log.info('Successfully deleted gist!')
284+
return 0
285+
210286
log.error('Unknown action.')
211287
log.error('Please consult help page (--help).')
212288
return 1

0 commit comments

Comments
 (0)