Merge lp:~ricardokirkner/locolander/add-author into lp:locolander

Proposed by Ricardo Kirkner
Status: Superseded
Proposed branch: lp:~ricardokirkner/locolander/add-author
Merge into: lp:locolander
Diff against target: 658 lines (+339/-108)
15 files modified
.bzrignore (+1/-0)
.locolander.yml (+1/-22)
README (+36/-46)
TODO (+43/-0)
config/supervisord.conf (+37/-0)
fabfile.py (+25/-0)
locolander/locolander/settings.py (+9/-10)
locolander/locolander/urls.py (+4/-0)
locolander/locolanderweb/migrations/0003_auto__chg_field_project_url__add_field_mergerequest_author.py (+93/-0)
locolander/locolanderweb/models.py (+1/-0)
locolander/locolanderweb/tests/test_base.py (+5/-2)
locolander/locolanderweb/tests/test_models.py (+5/-1)
locolander/repos/launchpad.py (+16/-1)
locolander/repos/tests/test_launchpad.py (+42/-23)
requirements.txt (+21/-3)
To merge this branch: bzr merge lp:~ricardokirkner/locolander/add-author
Reviewer Review Type Date Requested Status
LocoLanderos Pending
Review via email: mp+174569@code.launchpad.net
To post a comment you must log in.
20. By Ricardo Kirkner

expose locolanderweb models in admin

21. By Ricardo Kirkner

merged trunk

22. By Ricardo Kirkner

regenerated migration

23. By Ricardo Kirkner

merged trunk

24. By Ricardo Kirkner

reverted unwanted changes

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2013-06-21 22:23:29 +0000
3+++ .bzrignore 2013-07-13 17:31:27 +0000
4@@ -1,2 +1,3 @@
5 docker/files/ssh
6 locolander.db
7+supervisord.log
8
9=== modified file '.locolander.yml'
10--- .locolander.yml 2013-06-29 22:14:32 +0000
11+++ .locolander.yml 2013-07-13 17:31:27 +0000
12@@ -1,26 +1,5 @@
13 precise:
14- pip:
15- distribute: 0.6.34
16- Django: 1.5.1
17- github3.py: 0.7.0
18- httplib2: 0.8.0
19- keyring: 1.5.0
20- launchpadlib: 1.10.2
21- lazr.authentication: 0.1.2
22- lazr.restfulclient: 0.13.3
23- lazr.uri: 1.0.3
24- mock: 1.0.1
25- oauth: 1.0.1
26- pep8: 1.4.5
27- pyflakes: 0.7.2
28- PyYAML: 3.10.0
29- requests: 1.2.3
30- simplejson: 3.3.0
31- South: 0.8.1
32- testresources: 0.2.7
33- wadllib: 1.3.2
34- wsgi-intercept: 0.5.1
35- zope.interface: 4.0.5
36+ pip: -r requirements.txt
37
38 metadata:
39 test_script: ./run_tests.sh
40
41=== modified file 'README'
42--- README 2013-06-21 20:26:50 +0000
43+++ README 2013-07-13 17:31:27 +0000
44@@ -1,50 +1,40 @@
45
46+==========
47 LocoLander
48 ==========
49
50-.. image:: locolander/locolanderweb/media/locolander.png
51-
52-
53-To Do
54-=====
55-
56-* automate docker bootstrap
57-
58- - install docker
59- - install squid
60- - setup squid as a global cache
61-
62-* generate Dockerfile
63-
64- - parse custom project file
65- - write Dockerfile for project image
66- - include "env http_proxy http://172.16.42.1:3128" in Dockerfile to use global squid cache
67-
68-* build project images
69-
70- - use generated Dockerfile to run 'docker build'
71-
72- - step1: install base image and system packages
73- - step2: download pip dependencies from PyPI
74- - step3: cut internet access
75- - step4: install pip dependencies from locally downloaded cache
76-
77- - tag built image with project name
78-
79-* main script
80-
81- - get target branch (trunk)
82- - merge source branch into target
83- - run tests
84- - capture output
85- - commit merge and push to target upstream
86-
87-* main process
88-
89- - build project image
90- - run main script in image
91- - extract output from container
92-
93-* prevent inbound/outbound network access inside of container
94-
95-* implement quotas (disk, cpu, mem)
96+.. image::- locolander/locolanderweb/media/locolander.png
97+
98+
99+Installing
100+==========
101+
102+As redis cannot be installed in a virtualenv, you'll need to install it from
103+the system packages
104+::
105+
106+ sudo apt-get install redis-server
107+
108+Everything else can be installed in a virtualenv
109+::
110+
111+ sudo apt-get install fabric
112+ fab bootstrap
113+
114+
115+Running
116+=======
117+
118+Once everything is installed, you can start the full stack easily
119+::
120+
121+ fab start_supervisor
122+
123+
124+Developing
125+==========
126+
127+In order to run the tests, you can just do
128+::
129+
130+ fab test
131
132=== added file 'TODO'
133--- TODO 1970-01-01 00:00:00 +0000
134+++ TODO 2013-07-13 17:31:27 +0000
135@@ -0,0 +1,43 @@
136+To Do
137+=====
138+
139+* automate docker bootstrap
140+
141+ - install docker
142+ - install squid
143+ - setup squid as a global cache
144+
145+* generate Dockerfile
146+
147+ - parse custom project file
148+ - write Dockerfile for project image
149+ - include "env http_proxy http://172.16.42.1:3128" in Dockerfile to use global squid cache
150+
151+* build project images
152+
153+ - use generated Dockerfile to run 'docker build'
154+
155+ - step1: install base image and system packages
156+ - step2: download pip dependencies from PyPI
157+ - step3: cut internet access
158+ - step4: install pip dependencies from locally downloaded cache
159+
160+ - tag built image with project name
161+
162+* main script
163+
164+ - get target branch (trunk)
165+ - merge source branch into target
166+ - run tests
167+ - capture output
168+ - commit merge and push to target upstream
169+
170+* main process
171+
172+ - build project image
173+ - run main script in image
174+ - extract output from container
175+
176+* prevent inbound/outbound network access inside of container
177+
178+* implement quotas (disk, cpu, mem)
179
180=== added directory 'config'
181=== added file 'config/supervisord.conf'
182--- config/supervisord.conf 1970-01-01 00:00:00 +0000
183+++ config/supervisord.conf 2013-07-13 17:31:27 +0000
184@@ -0,0 +1,37 @@
185+; Sample supervisor config file.
186+
187+[supervisord]
188+nodaemon = true
189+loglevel = info
190+logfile = supervisord.log
191+
192+
193+; service configuration sections
194+
195+[program:gunicorn]
196+command = gunicorn_django locolander/locolander/settings.py
197+autostart = true
198+autorestart = true
199+stdout_logfile = supervisord.log
200+redirect_stderr = true
201+
202+[program:redis]
203+command = redis-server
204+autostart = true
205+autorestart = true
206+stdout_logfile = supervisord.log
207+redirect_stderr = true
208+
209+[program:celeryd]
210+command = python locolander/manage.py celeryd
211+autostart = true
212+autorestart = true
213+stdout_logfile = supervisord.log
214+redirect_stderr = true
215+
216+[program:flower]
217+command = python locolander/manage.py celery flower
218+autostart = true
219+autorestart = true
220+stdout_logfile = supervisord.log
221+redirect_stderr = true
222
223=== modified file 'fabfile.py'
224--- fabfile.py 2013-07-09 18:42:01 +0000
225+++ fabfile.py 2013-07-13 17:31:27 +0000
226@@ -24,3 +24,28 @@
227 @virtualenv
228 def test():
229 local('./run_tests.sh')
230+
231+
232+@virtualenv
233+def start_redis():
234+ local('redis-server')
235+
236+
237+@virtualenv
238+def start_gunicorn():
239+ local('gunicorn_django locolander/locolander/settings.py')
240+
241+
242+@virtualenv
243+def start_celery():
244+ manage('celeryd')
245+
246+
247+@virtualenv
248+def start_flower():
249+ manage('celery flower')
250+
251+
252+@virtualenv
253+def start_supervisor():
254+ local('supervisord -c config/supervisord.conf -n')
255
256=== modified file 'locolander/locolander/settings.py'
257--- locolander/locolander/settings.py 2013-06-23 00:00:56 +0000
258+++ locolander/locolander/settings.py 2013-07-13 17:31:27 +0000
259@@ -2,9 +2,6 @@
260
261 import os
262
263-from django.core.urlresolvers import reverse
264-
265-
266 PROJECT_DIR = os.path.abspath(os.path.dirname(__file__))
267
268 DEBUG = True
269@@ -134,6 +131,7 @@
270 # 'django.contrib.admindocs',
271 'locolanderweb',
272 'south',
273+ 'djcelery',
274 )
275
276 # A sample logging configuration. The only tangible logging
277@@ -165,10 +163,11 @@
278 }
279 }
280
281-LOGIN_URL = reverse('login')
282-LOGIN_REDIRECT_URL = reverse('home')
283-
284-try:
285- from local_setting import *
286-except:
287- pass
288+# settings for celery/redis
289+import djcelery
290+djcelery.setup_loader()
291+
292+BROKER_URL = 'redis://localhost:6379/0'
293+
294+LOGIN_URL = '/login/'
295+LOGIN_REDIRECT_URL = '/'
296
297=== modified file 'locolander/locolander/urls.py'
298--- locolander/locolander/urls.py 2013-06-23 00:00:56 +0000
299+++ locolander/locolander/urls.py 2013-07-13 17:31:27 +0000
300@@ -1,5 +1,6 @@
301 from django.conf.urls import patterns, include, url
302 from django.contrib import admin
303+from django.contrib.staticfiles.urls import staticfiles_urlpatterns
304 admin.autodiscover()
305
306
307@@ -14,3 +15,6 @@
308 # Uncomment the next line to enable the admin:
309 url(r'^admin/', include(admin.site.urls)),
310 )
311+
312+# enable staticfiles for gunicorn
313+urlpatterns += staticfiles_urlpatterns()
314
315=== added file 'locolander/locolanderweb/migrations/0003_auto__chg_field_project_url__add_field_mergerequest_author.py'
316--- locolander/locolanderweb/migrations/0003_auto__chg_field_project_url__add_field_mergerequest_author.py 1970-01-01 00:00:00 +0000
317+++ locolander/locolanderweb/migrations/0003_auto__chg_field_project_url__add_field_mergerequest_author.py 2013-07-13 17:31:27 +0000
318@@ -0,0 +1,93 @@
319+# -*- coding: utf-8 -*-
320+from south.db import db
321+from south.v2 import SchemaMigration
322+
323+
324+class Migration(SchemaMigration):
325+
326+ def forwards(self, orm):
327+
328+ # Changing field 'Project.url'
329+ db.alter_column(u'locolanderweb_project', 'url', self.gf('django.db.models.fields.CharField')(max_length=256))
330+ # Adding field 'MergeRequest.author'
331+ db.add_column(u'locolanderweb_mergerequest', 'author',
332+ self.gf('django.db.models.fields.TextField')(default=''),
333+ keep_default=False)
334+
335+
336+ def backwards(self, orm):
337+
338+ # Changing field 'Project.url'
339+ db.alter_column(u'locolanderweb_project', 'url', self.gf('django.db.models.fields.URLField')(max_length=200))
340+ # Deleting field 'MergeRequest.author'
341+ db.delete_column(u'locolanderweb_mergerequest', 'author')
342+
343+
344+ models = {
345+ u'auth.group': {
346+ 'Meta': {'object_name': 'Group'},
347+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
348+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
349+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
350+ },
351+ u'auth.permission': {
352+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
353+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
354+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
355+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
356+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
357+ },
358+ u'auth.user': {
359+ 'Meta': {'object_name': 'User'},
360+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
361+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
362+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
363+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
364+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
365+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
366+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
367+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
368+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
369+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
370+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
371+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
372+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
373+ },
374+ u'contenttypes.contenttype': {
375+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
376+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
377+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
378+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
379+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
380+ },
381+ u'locolanderweb.mergerequest': {
382+ 'Meta': {'object_name': 'MergeRequest'},
383+ 'author': ('django.db.models.fields.TextField', [], {'default': "''"}),
384+ 'commit_message': ('django.db.models.fields.TextField', [], {}),
385+ 'date_completed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
386+ 'date_created': ('django.db.models.fields.DateTimeField', [], {}),
387+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
388+ 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['locolanderweb.Project']"}),
389+ 'reviewers': ('django.db.models.fields.TextField', [], {}),
390+ 'source': ('django.db.models.fields.TextField', [], {}),
391+ 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '128'}),
392+ 'target': ('django.db.models.fields.TextField', [], {})
393+ },
394+ u'locolanderweb.project': {
395+ 'Meta': {'object_name': 'Project'},
396+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
397+ 'name': ('django.db.models.fields.TextField', [], {}),
398+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}),
399+ 'url': ('django.db.models.fields.CharField', [], {'max_length': '256'})
400+ },
401+ u'locolanderweb.runlog': {
402+ 'Meta': {'object_name': 'RunLog'},
403+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
404+ 'merge_request': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['locolanderweb.MergeRequest']"}),
405+ 'raw': ('django.db.models.fields.TextField', [], {}),
406+ 'return_code': ('django.db.models.fields.IntegerField', [], {}),
407+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
408+ }
409+ }
410+
411+ complete_apps = ['locolanderweb']
412
413=== modified file 'locolander/locolanderweb/models.py'
414--- locolander/locolanderweb/models.py 2013-07-06 23:19:29 +0000
415+++ locolander/locolanderweb/models.py 2013-07-13 17:31:27 +0000
416@@ -55,6 +55,7 @@
417 source = models.TextField()
418 target = models.TextField()
419 commit_message = models.TextField()
420+ author = models.TextField(default='')
421 reviewers = models.TextField()
422 date_created = models.DateTimeField()
423 date_completed = models.DateTimeField(null=True, blank=True)
424
425=== modified file 'locolander/locolanderweb/tests/test_base.py'
426--- locolander/locolanderweb/tests/test_base.py 2013-07-06 23:19:29 +0000
427+++ locolander/locolanderweb/tests/test_base.py 2013-07-13 17:31:27 +0000
428@@ -46,7 +46,8 @@
429 def make_merge_request(
430 self, project=None, status=STATUS_PENDING,
431 source=None, target=None, commit_message=None,
432- reviewers=None, date_created=None, date_completed=None):
433+ author=None, reviewers=None, date_created=None,
434+ date_completed=None):
435 if project is None:
436 project = self.make_project()
437 if source is None:
438@@ -55,13 +56,15 @@
439 target = 'http://foo.target'
440 if commit_message is None:
441 commit_message = 'Foo bar commit message.'
442+ if author is None:
443+ author = 'Some Person <some@domain>'
444 if reviewers is None:
445 reviewers = ['mengano', 'fulano']
446 if date_created is None:
447 date_created = datetime.utcnow()
448 return MergeRequest.objects.create(
449 project=project, status=status, source=source, target=target,
450- commit_message=commit_message, reviewers=reviewers,
451+ commit_message=commit_message, author=author, reviewers=reviewers,
452 date_created=date_created, date_completed=date_completed)
453
454 def make_run_log(self, merge_request=None, return_code=0, raw=None):
455
456=== modified file 'locolander/locolanderweb/tests/test_models.py'
457--- locolander/locolanderweb/tests/test_models.py 2013-07-06 23:19:29 +0000
458+++ locolander/locolanderweb/tests/test_models.py 2013-07-13 17:31:27 +0000
459@@ -120,7 +120,8 @@
460 self.obj = MergeRequest.objects.create(
461 project=self.project,
462 source='source', target='target', commit_message='commit',
463- reviewers='fulano, mengano', date_created=self.created)
464+ author='Some Person <some@domain>', reviewers='fulano, mengano',
465+ date_created=self.created)
466
467 def test_status_default(self):
468 self.assertEqual(self.obj.status, STATUS_PENDING)
469@@ -139,6 +140,9 @@
470 def test_commit_message(self):
471 self.assertEqual(self.obj.commit_message, 'commit')
472
473+ def test_author(self):
474+ self.assertEqual(self.obj.author, 'Some Person <some@domain>')
475+
476 def test_reviewers(self):
477 self.assertEqual(self.obj.reviewers, 'fulano, mengano')
478
479
480=== modified file 'locolander/repos/launchpad.py'
481--- locolander/repos/launchpad.py 2013-07-06 23:19:29 +0000
482+++ locolander/repos/launchpad.py 2013-07-13 17:31:27 +0000
483@@ -5,6 +5,10 @@
484 import os
485 import re
486
487+from datetime import datetime
488+
489+import bzrlib.plugin
490+from bzrlib.branch import Branch
491 from launchpadlib.launchpad import Launchpad
492
493 from repos import errors
494@@ -29,6 +33,14 @@
495 self.launchpad = Launchpad.login_anonymously(
496 'locolander', 'production', CACHE_DIR)
497 self.lp_projects = self.launchpad.projects
498+ # load bzr plugins so that lp: namespace is recognized
499+ bzrlib.plugin.load_plugins()
500+
501+ def get_author(self, branch_url):
502+ branch = Branch.open(branch_url)
503+ tip = branch.repository.get_revision(
504+ branch.get_rev_id(branch.revno()))
505+ return tip.committer
506
507 def approved_requests_for_project_url(self, project_url):
508 """Return approved merge proposals."""
509@@ -49,12 +61,15 @@
510 proposals = repo.getMergeProposals()
511 for mp in proposals:
512 if mp.queue_status.lower() == 'approved':
513+ created = mp.date_created
514 approved.append({
515 # XXX: needs UTC processing?
516- 'date_created': mp.date_created,
517+ 'date_created': datetime(created.year, created.month,
518+ created.day),
519 'source': mp.source_branch.bzr_identity,
520 'target': mp.target_branch.bzr_identity,
521 'reviewers': mp.reviewer.name, # csv
522 'commit_message': mp.commit_message,
523+ 'author': self.get_author(mp.source_branch.bzr_identity),
524 })
525 return approved
526
527=== modified file 'locolander/repos/tests/test_launchpad.py'
528--- locolander/repos/tests/test_launchpad.py 2013-07-06 22:26:19 +0000
529+++ locolander/repos/tests/test_launchpad.py 2013-07-13 17:31:27 +0000
530@@ -3,11 +3,16 @@
531 from datetime import datetime
532 from unittest import TestCase
533
534+import bzrlib.branch
535 from mock import Mock, patch
536
537 from repos import errors, launchpad
538
539
540+# fake bzrlib Branch object
541+FakeBranch = Mock(spec=bzrlib.branch.Branch)
542+
543+
544 class FakeMP(object):
545 """Fake launchpadlib Merge Proposal object."""
546
547@@ -71,26 +76,40 @@
548 with self.assertRaises(errors.RepositoryDoesNotExist):
549 self.lp.approved_requests_for_project_url('lp:not-found')
550
551- def test_valid_project_without_approved_mp(self):
552- mps = self.lp.approved_requests_for_project_url('lp:test-project')
553- self.assertEqual(len(mps), 1)
554- mp = mps[0]
555- self.assertIsInstance(mp, dict)
556-
557- expected_keys = ['date_created', 'source', 'target', 'reviewers',
558- 'commit_message']
559- for key in expected_keys:
560- self.assertIn(key, mp)
561- self.assertEqual(mp['commit_message'], 'approved mp')
562-
563- def test_valid_project_return_approved_mp(self):
564- mps = self.lp.approved_requests_for_project_url('lp:test-project')
565- self.assertEqual(len(mps), 1)
566- mp = mps[0]
567- self.assertIsInstance(mp, dict)
568-
569- expected_keys = ['date_created', 'source', 'target', 'reviewers',
570- 'commit_message']
571- for key in expected_keys:
572- self.assertIn(key, mp)
573- self.assertEqual(mp['commit_message'], 'approved mp')
574+ @patch('repos.launchpad.Branch.open')
575+ def test_valid_project_without_approved_mp(self, mock_open):
576+ mps = self.lp.approved_requests_for_project_url('lp:test-project')
577+ self.assertEqual(len(mps), 1)
578+ mp = mps[0]
579+ self.assertIsInstance(mp, dict)
580+
581+ expected_keys = ['date_created', 'source', 'target', 'reviewers',
582+ 'commit_message', 'author']
583+ for key in expected_keys:
584+ self.assertIn(key, mp)
585+ self.assertEqual(mp['commit_message'], 'approved mp')
586+
587+ @patch('repos.launchpad.Branch.open')
588+ def test_valid_project_return_approved_mp(self, mock_open):
589+ mps = self.lp.approved_requests_for_project_url('lp:test-project')
590+ self.assertEqual(len(mps), 1)
591+ mp = mps[0]
592+ self.assertIsInstance(mp, dict)
593+
594+ expected_keys = ['date_created', 'source', 'target', 'reviewers',
595+ 'commit_message', 'author']
596+ for key in expected_keys:
597+ self.assertIn(key, mp)
598+ self.assertEqual(mp['commit_message'], 'approved mp')
599+
600+ @patch('repos.launchpad.Branch.open')
601+ def test_get_author(self, mock_open):
602+ mock_revision = Mock()
603+ mock_revision.committer = 'The Author <some@email.com>'
604+ mock_repository = Mock()
605+ mock_repository.get_revision.return_value = mock_revision
606+ mock_open.return_value.repository = mock_repository
607+
608+ author = self.lp.get_author('lp:test-project')
609+
610+ self.assertEqual(author, 'The Author <some@email.com>')
611
612=== modified file 'requirements.txt'
613--- requirements.txt 2013-07-13 15:38:09 +0000
614+++ requirements.txt 2013-07-13 17:31:27 +0000
615@@ -1,22 +1,40 @@
616-distribute==0.6.34
617+amqp==1.0.12
618+anyjson==0.3.3
619+billiard==2.7.3.31
620+bzr==2.6b2
621+celery==3.0.21
622+cssselect==0.8
623 Django==1.5.1
624+django-celery==3.0.17
625+flower==0.5.2
626 github3.py==0.7.0
627+gunicorn==17.5
628 httplib2==0.8
629 keyring==1.5
630+kombu==2.5.12
631 launchpadlib==1.10.2
632 lazr.authentication==0.1.2
633 lazr.restfulclient==0.13.3
634 lazr.uri==1.0.3
635+lxml==3.2.1
636+meld3==0.6.10
637 mock==1.0.1
638 oauth==1.0.1
639-pep8==1.4.5
640-pyflakes==0.7.2
641+pep8==1.4.6
642+pyflakes==0.7.3
643 pyquery==1.2.4
644+python-dateutil==2.1
645+pytz==2013b
646 PyYAML==3.10
647+redis==2.7.6
648 requests==1.2.3
649 simplejson==3.3.0
650+six==1.3.0
651 South==0.8.1
652+supervisor==3.0b2
653 testresources==0.2.7
654+tornado==3.1
655 wadllib==1.3.2
656 wsgi-intercept==0.5.1
657+wsgiref==0.1.2
658 zope.interface==4.0.5

Subscribers

People subscribed via source and target branches

to all changes: