Merge lp:~savoirfairelinux-openerp/lp-community-utils/nag_refactor into lp:lp-community-utils

Status: Superseded
Proposed branch: lp:~savoirfairelinux-openerp/lp-community-utils/nag_refactor
Merge into: lp:lp-community-utils
Diff against target: 508 lines (+254/-223)
2 files modified
lp.py (+252/-0)
openerp-nag (+2/-223)
To merge this branch: bzr merge lp:~savoirfairelinux-openerp/lp-community-utils/nag_refactor
Reviewer Review Type Date Requested Status
Guewen Baconnier @ Camptocamp code review Needs Fixing
Review via email: mp+205257@code.launchpad.net

This proposal has been superseded by a proposal from 2014-04-06.

Description of the change

Cleaned up code of nag
* Put branch analysis code in nag_lib
* Changed Nag to a class
* Removed unused parameters
* Fixed sign/sort issue with age
* General readability improvements

This will allow for other scripts that the general nag script to use some shared functionality

To post a comment you must log in.
24. By Sandy Carter (http://www.savoirfairelinux.com)

Adds handy progressbar if progressbar library is available

25. By Guewen Baconnier @ Camptocamp

[MRG] Display the votes of the reviews in the nag list, display a different sentence for the proposals passing the merging rules

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

Sorry I didn't saw your proposal before (even if I use openerp-nag...) whereas it deserves some attention.
Thanks for your work so far.

Seems good to me even if some changes seems not really necessary to me, surely you had some reasons.

What is the rationale to change the Nag namedtuple to a class? A reason could be that you added methods, but not. On that topic, couldn't we move the big print of the end to a method on Nag() (it seems that it could even be the __str__).

Couldn't already the module be used as a lib? (as there is if __name__ == "__main__":) If the filename is an issue (it is, i we want to import it), maybe we can consider to rename it? Not that I strongly disagree with the split but it seem a bit overkill to me here. But maybe do you have great plans to extend this tool and so it makes more sense ;-)

I strongly disagree to remove the --authenticated option: the Launchpad API is limited with anonymous users and it is used now for the votes (the votes are only visible by authenticated users). BTW you may want to rebase on the master since votes have been integrated meanwhile.

What was the bug with the age?

review: Needs Fixing (code review)
Revision history for this message
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote :

@gbaconnier-c2c
Good point about adding functions to Nag, the reason for turning it into a class is design and optimization.
It gives the option, like to said, to add functions but also, the ability to inherit from it.

I do have big plans with the lib, for example, automatic diff-validating in the style of (https://github.com/torvalds/linux/blob/master/scripts/checkpatch.pl), specifically checking style, seeing if the diff introduces pep8 errors, removes them, testing the patches with jenkins, commenting on the MPs, etc.
I have proposed something already: https://code.launchpad.net/~savoirfairelinux-openerp/lp-community-utils/branch_pep8/+merge/205260

As for the --authenticated option, I think I took it away because it wasn't referenced or used, I can put it back now, however I will have no impact. This was a while ago, though, I could have just taken it out accidentally.

The problem with age was that it declared it negatively, then did * -1 again while sorting. I just took out the (-1 * -1 = 1) redundancy. It also has a one off error where an MP from today is displayed as 1 day old.

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

 > On 03/07/2014 07:45 PM, Sandy Carter (http://www.savoirfairelinux.com)
> wrote:
> @gbaconnier-c2c
> Good point about adding functions to Nag, the reason for turning it
> into a class is design and optimization.
The rationale about design is very subjective and the one about
optimization is dubious:

http://hastebin.com/neyedexiju.py

Anyway, if it needs methods, and I think the display should be one, then
it makes sense.

> It gives the option, like to said, to add functions but also, the
> ability to inherit from it.
>
> I do have big plans with the lib, for example, automatic
> diff-validating in the style of
> (https://github.com/torvalds/linux/blob/master/scripts/checkpatch.pl),
> specifically checking style, seeing if the diff introduces pep8
> errors, removes them, testing the patches with jenkins, commenting on
> the MPs, etc.
> I have proposed something already:
> https://code.launchpad.net/~savoirfairelinux-openerp/lp-community-utils/branch_pep8/+merge/205260
>
>
> As for the --authenticated option, I think I took it away because it
> wasn't referenced or used, I can put it back now, however I will have
> no impact. This was a while ago, though, I could have just taken it
> out accidentally.

That's right at the time you made the proposal, the option seemed to be
not useful.

>
> The problem with age was that it declared it negatively, then did * -1
> again while sorting. I just took out the (-1 * -1 = 1) redundancy. It
> also has a one off error where an MP from today is displayed as 1 day
> old.

Ok.

Revision history for this message
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote :

> > On 03/07/2014 07:45 PM, Sandy Carter (http://www.savoirfairelinux.com)
> > wrote:
> > @gbaconnier-c2c
> > Good point about adding functions to Nag, the reason for turning it
> > into a class is design and optimization.
> The rationale about design is very subjective and the one about
> optimization is dubious:
>
> http://hastebin.com/neyedexiju.py

Declaring a class puts members together in memory and reduces the rate of cache misses, you can see this when performing lookups, but mostly when doing operations.
Try this: http://hastebin.com/difomedika.py
That's close to 15% increase in speed for summing members.

>
> Anyway, if it needs methods, and I think the display should be one, then
> it makes sense.
>

I agree

> > It gives the option, like to said, to add functions but also, the
> > ability to inherit from it.
> >
> > I do have big plans with the lib, for example, automatic
> > diff-validating in the style of
> > (https://github.com/torvalds/linux/blob/master/scripts/checkpatch.pl),
> > specifically checking style, seeing if the diff introduces pep8
> > errors, removes them, testing the patches with jenkins, commenting on
> > the MPs, etc.
> > I have proposed something already:
> > https://code.launchpad.net/~savoirfairelinux-openerp/lp-community-
> utils/branch_pep8/+merge/205260
> >
> >
> > As for the --authenticated option, I think I took it away because it
> > wasn't referenced or used, I can put it back now, however I will have
> > no impact. This was a while ago, though, I could have just taken it
> > out accidentally.
>
> That's right at the time you made the proposal, the option seemed to be
> not useful.
>
> >
> > The problem with age was that it declared it negatively, then did * -1
> > again while sorting. I just took out the (-1 * -1 = 1) redundancy. It
> > also has a one off error where an MP from today is displayed as 1 day
> > old.
>
> Ok.

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

On 03/10/2014 02:12 PM, Sandy Carter (http://www.savoirfairelinux.com)
wrote:
>> > On 03/07/2014 07:45 PM, Sandy Carter (http://www.savoirfairelinux.com)
>>> wrote:
>>> @gbaconnier-c2c
>>> Good point about adding functions to Nag, the reason for turning it
>>> into a class is design and optimization.
>> The rationale about design is very subjective and the one about
>> optimization is dubious:
>>
>> http://hastebin.com/neyedexiju.py
>
> Declaring a class puts members together in memory and reduces the rate of cache misses, you can see this when performing lookups, but mostly when doing operations.
> Try this: http://hastebin.com/difomedika.py
> That's close to 15% increase in speed for summing members.
>

Enlightening, thanks for the explanation.

26. By Sandy Carter (http://www.savoirfairelinux.com)

[IMP] PEP 394 (partial) invoke env instead of system python

27. By Yannick Vaucher @ Camptocamp

add multi-company project

28. By Sandy Carter (http://www.savoirfairelinux.com)

[MRG] A tool I use to quickly check pep8 on MPs.

A few people have asked what tool I use, so I though I would put it up here.

The command I use is:
./checkout-flake8.sh lp:launchpad_openerp_repo module_name
for example:
./checkout-flake8.sh lp:~savoirfairelinux-openerp/partner-contact-management/user-firstname/+merge/210710 partner_firstname

It checks it out in /tmp and runs flake8 on the module

Revision history for this message
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote :

@Guewen
> Couldn't already the module be used as a lib? (as there is if __name__ ==
> "__main__":) If the filename is an issue (it is, i we want to import it),
> maybe we can consider to rename it?

The problem right now is that you can't import from it due to its filename (dash in filename and no .py)

29. By Sandy Carter (http://www.savoirfairelinux.com)

Moved common code out of openerp-nag into lp.py so it can be used by other scripts

Unmerged revisions

29. By Sandy Carter (http://www.savoirfairelinux.com)

Moved common code out of openerp-nag into lp.py so it can be used by other scripts

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'lp.py'
--- lp.py 1970-01-01 00:00:00 +0000
+++ lp.py 2014-04-06 16:36:47 +0000
@@ -0,0 +1,252 @@
1# Copyright 2012 Canonical Ltd.
2# Written by:
3# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
4# Hacked for the OpenERP Community Reviewers version by:
5# Guewen Baconnier <guewen.baconnier@camptocamp.com>
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 3,
9# as published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19
20from __future__ import unicode_literals, print_function
21
22import collections
23import datetime
24import logging
25
26try:
27 from progressbar import ProgressBar, Bar, Percentage, ETA
28except ImportError:
29 ProgressBar = None
30
31from launchpadlib.launchpad import Launchpad
32
33
34# Nag:
35# Indication that we want to nag the 'person'...
36# ...with the specified 'action'
37# ...about a particular 'subject'
38Nag = collections.namedtuple(
39 "Nag", "person action subject sort_class sort_priority sort_age project_name votes")
40
41SORT_CLASS_BUG, SORT_CLASS_MERGE = range(2)
42
43
44def show_lp_object(obj):
45 print(obj)
46 print("attributes:", obj.lp_attributes)
47 for attr in obj.lp_attributes:
48 print("attribute[%s]: %s" % (attr, getattr(obj, attr)))
49 print("entries:", obj.lp_entries)
50 for entry in obj.lp_entries:
51 print("entry[%s]: %s" % (entry, getattr(obj, entry)))
52 print("collections:", obj.lp_collections)
53 print("operations:", obj.lp_operations)
54 print("-" * 80)
55
56
57class DefaultPolicy:
58
59 mps_need_commit_message = False
60
61 max_review_age = 0
62
63 max_bug_new_age = 7
64
65 # minimal number of reviewers before merging
66 min_approve = 2
67
68 # can't be merged before n days
69 days_before_merge = 5
70
71 # can be merged before days_before_merge if at least n approvals
72 approvals_to_bypass = 3
73
74
75class UTC(datetime.tzinfo):
76
77 def dst(self, foo):
78 return datetime.timedelta(0, 0)
79
80 def utcoffset(self, foo):
81 return datetime.timedelta(0, 0)
82
83
84UTC = UTC()
85
86
87class Votes(object):
88 """ Holds votes of a proposal """
89
90 __states__ = {
91 'Approve': 'approve',
92 'Needs Fixing': 'needs_fixing',
93 'Needs Information': 'needs_information',
94 'Abstain': 'abstain',
95 'Disapprove': 'disapprove',
96 'Resubmit': 'resubmit',
97 'Pending': 'pending',
98 }
99
100 __signs__ = {
101 'approve': '+',
102 'needs_fixing': '!',
103 'needs_information': '?',
104 'abstain': '~',
105 'disapprove': '-',
106 'resubmit': 'R',
107 'pending': '*',
108 }
109
110 __ignore_for_approval__ = ['abstain', 'pending']
111
112 def __init__(self, votes):
113 self._votes = collections.Counter(
114 self.__states__[vote.comment.vote if vote.comment else 'Pending']
115 for vote in votes
116 ) # no comment is a pending review, usually the team
117
118 def __getattr__(self, name):
119 if name in self._votes:
120 return self._votes[name]
121 elif name in self.__states__.values():
122 return 0
123 else:
124 raise AttributeError
125
126 def __str__(self):
127 signs = ['%d%s' % (count, self.__signs__[state]) for
128 state, count in self._votes.iteritems()]
129 return ', '.join(signs)
130
131 def total(self, for_approval=False):
132 if for_approval:
133 return sum(count for state, count in self._votes.iteritems()
134 if state not in self.__ignore_for_approval__)
135 return sum(self._votes.values())
136
137 @classmethod
138 def legend(cls):
139 return dict((state, cls.__signs__[code]) for
140 state, code in cls.__states__.iteritems())
141
142
143def gen_project_nags(lp, policy, project_name):
144 # TODO: Detect project groups and redirect the nag to all projects
145 # underneath that project
146 # Access the project that we care about
147 logging.debug("Accessing project %r", project_name)
148 project = lp.projects[project_name]
149 # Re-yield all the merge proposal nags
150 # NOTE: change to yield from in py3k3
151 for nag in gen_merge_proposal_nags(lp, policy, project):
152 yield nag
153
154
155def gen_merge_proposal_nags(lp, policy, project):
156 # XXX: cannnot use utcnow as launchpad returns tz-aware objects here
157 now = datetime.datetime.now(tz=UTC)
158 logging.debug("Looking for merge proposals in project %r", project)
159 # NOTE: Workaround for project.getMergeProposals() crashing on timeouts
160 try:
161 merge_proposals = project.getMergeProposals(status='Needs review')
162 except AttributeError:
163 logging.warning("The project %s has no code to look at", project)
164 return
165 for proposal in merge_proposals:
166 logging.debug("Looking at merge proposal %r", proposal)
167 # Skip everything that is still not requested for review
168 if proposal.date_review_requested is None:
169 continue
170 # Skip everything that is already merged
171 if proposal.date_merged is not None:
172 continue
173 votes = Votes(proposal.votes)
174
175 # Nag about missing commit message on merge requests
176 if policy.mps_need_commit_message and proposal.commit_message is None:
177 yield Nag(
178 person=proposal.registrant.display_name,
179 action="set a commit message on merge request",
180 subject=proposal.web_link,
181 sort_class=SORT_CLASS_MERGE,
182 sort_priority=None, # TODO: get from max(linked bugs)
183 sort_age=(proposal.date_review_requested - now).days,
184 project_name=project.name,
185 votes=votes,
186 )
187
188 age = (now - proposal.date_review_requested).days
189
190 if (votes.approve == votes.total(for_approval=True) and
191 (votes.approve >= policy.approvals_to_bypass or
192 votes.approve >= policy.min_approve and
193 age >= policy.days_before_merge)):
194 yield Nag(
195 person='A committer',
196 action="consider to merge the proposal",
197 subject=proposal.web_link,
198 sort_class=SORT_CLASS_MERGE,
199 sort_priority=None, # TODO: get from max(linked bugs)
200 sort_age=(proposal.date_review_requested - now).days,
201 project_name=project.name,
202 votes=votes,
203 )
204 continue
205
206 # Nag about aging merge requests
207 if age >= policy.max_review_age:
208 yield Nag(
209 person='Someone',
210 action="review the merge request",
211 subject=proposal.web_link,
212 sort_class=SORT_CLASS_MERGE,
213 sort_priority=None, # TODO: get from max(linked bugs)
214 sort_age=(proposal.date_review_requested - now).days,
215 project_name=project.name,
216 votes=votes,
217 )
218
219
220def gen_bug_nags(lp, policy, project):
221 now = datetime.datetime.now(tz=UTC)
222 new_threshold = now - datetime.timedelta(days=policy.max_bug_new_age)
223 # Nag about bugs that are "New" for too long
224 logging.debug("Looking at 'new' bugs that are 8 days or older")
225 aging_new_bugs = project.searchTasks(status='New',
226 created_before=new_threshold)
227 for bug_task in aging_new_bugs:
228 yield Nag(
229 person='everyone',
230 action='triage the bug',
231 subject=bug_task.web_link,
232 sort_class=SORT_CLASS_BUG,
233 sort_priority=None, # TODO: convert from importance name
234 sort_age=None,
235 votes=None)
236
237
238def parse_projects_file(filename):
239 """ Parse a file containing name of the projects
240 A line per project
241 """
242 with open(filename, mode='rU') as project_file:
243 content = project_file.read()
244
245 return [name.strip() for name in content.splitlines()]
246
247
248def launchpad_login(anonymous, consumer_name, service_root):
249 if anonymous:
250 return Launchpad.login_anonymously(consumer_name, service_root)
251 else:
252 return Launchpad.login_with(consumer_name, service_root)
0253
=== modified file 'openerp-nag'
--- openerp-nag 2014-02-20 16:18:06 +0000
+++ openerp-nag 2014-04-06 16:36:47 +0000
@@ -42,234 +42,16 @@
42from __future__ import unicode_literals, print_function42from __future__ import unicode_literals, print_function
4343
44import argparse44import argparse
45import collections
46import datetime
47import logging
4845
49try:46try:
50 from progressbar import ProgressBar, Bar, Percentage, ETA47 from progressbar import ProgressBar, Bar, Percentage, ETA
51except ImportError:48except ImportError:
52 ProgressBar = None49 ProgressBar = None
53from launchpadlib.launchpad import Launchpad50from lp import *
54
5551
56consumer_name = 'OpenERP Community Reviewers Nagging Scripts'52consumer_name = 'OpenERP Community Reviewers Nagging Scripts'
5753
5854
59# Nag:
60# Indication that we want to nag the 'person'...
61# ...with the specified 'action'
62# ...about a particular 'subject'
63Nag = collections.namedtuple(
64 "Nag", "person action subject sort_class sort_priority sort_age project_name votes")
65
66SORT_CLASS_BUG, SORT_CLASS_MERGE = range(2)
67
68
69def show_lp_object(obj):
70 print(obj)
71 print("attributes:", obj.lp_attributes)
72 for attr in obj.lp_attributes:
73 print("attribute[%s]: %s" % (attr, getattr(obj, attr)))
74 print("entries:", obj.lp_entries)
75 for entry in obj.lp_entries:
76 print("entry[%s]: %s" % (entry, getattr(obj, entry)))
77 print("collections:", obj.lp_collections)
78 print("operations:", obj.lp_operations)
79 print("-" * 80)
80
81
82class DefaultPolicy:
83
84 mps_need_commit_message = False
85
86 max_review_age = 0
87
88 max_bug_new_age = 7
89
90 # minimal number of reviewers before merging
91 min_approve = 2
92
93 # can't be merged before n days
94 days_before_merge = 5
95
96 # can be merged before days_before_merge if at least n approvals
97 approvals_to_bypass = 3
98
99
100class UTC(datetime.tzinfo):
101
102 def dst(self, foo):
103 return datetime.timedelta(0, 0)
104
105 def utcoffset(self, foo):
106 return datetime.timedelta(0, 0)
107
108
109UTC = UTC()
110
111
112class Votes(object):
113 """ Holds votes of a proposal """
114
115 __states__ = {
116 'Approve': 'approve',
117 'Needs Fixing': 'needs_fixing',
118 'Needs Information': 'needs_information',
119 'Abstain': 'abstain',
120 'Disapprove': 'disapprove',
121 'Resubmit': 'resubmit',
122 'Pending': 'pending',
123 }
124
125 __signs__ = {
126 'approve': '+',
127 'needs_fixing': '!',
128 'needs_information': '?',
129 'abstain': '~',
130 'disapprove': '-',
131 'resubmit': 'R',
132 'pending': '*',
133 }
134
135 __ignore_for_approval__ = ['abstain', 'pending']
136
137 def __init__(self, votes):
138 self._votes = collections.Counter(
139 self.__states__[vote.comment.vote if vote.comment else 'Pending']
140 for vote in votes
141 ) # no comment is a pending review, usually the team
142
143 def __getattr__(self, name):
144 if name in self._votes:
145 return self._votes[name]
146 elif name in self.__states__.values():
147 return 0
148 else:
149 raise AttributeError
150
151 def __str__(self):
152 signs = ['%d%s' % (count, self.__signs__[state]) for
153 state, count in self._votes.iteritems()]
154 return ', '.join(signs)
155
156 def total(self, for_approval=False):
157 if for_approval:
158 return sum(count for state, count in self._votes.iteritems()
159 if state not in self.__ignore_for_approval__)
160 return sum(self._votes.values())
161
162 @classmethod
163 def legend(cls):
164 return dict((state, cls.__signs__[code]) for
165 state, code in cls.__states__.iteritems())
166
167
168def gen_project_nags(lp, policy, project_name):
169 # TODO: Detect project groups and redirect the nag to all projects
170 # underneath that project
171 # Access the project that we care about
172 logging.debug("Accessing project %r", project_name)
173 project = lp.projects[project_name]
174 # Re-yield all the merge proposal nags
175 # NOTE: change to yield from in py3k3
176 for nag in gen_merge_proposal_nags(lp, policy, project):
177 yield nag
178
179
180def gen_merge_proposal_nags(lp, policy, project):
181 # XXX: cannnot use utcnow as launchpad returns tz-aware objects here
182 now = datetime.datetime.now(tz=UTC)
183 logging.debug("Looking for merge proposals in project %r", project)
184 # NOTE: Workaround for project.getMergeProposals() crashing on timeouts
185 try:
186 merge_proposals = project.getMergeProposals(status='Needs review')
187 except AttributeError:
188 logging.warning("The project %s has no code to look at", project)
189 return
190 for proposal in merge_proposals:
191 logging.debug("Looking at merge proposal %r", proposal)
192 # Skip everything that is still not requested for review
193 if proposal.date_review_requested is None:
194 continue
195 # Skip everything that is already merged
196 if proposal.date_merged is not None:
197 continue
198 votes = Votes(proposal.votes)
199
200 # Nag about missing commit message on merge requests
201 if policy.mps_need_commit_message and proposal.commit_message is None:
202 yield Nag(
203 person=proposal.registrant.display_name,
204 action="set a commit message on merge request",
205 subject=proposal.web_link,
206 sort_class=SORT_CLASS_MERGE,
207 sort_priority=None, # TODO: get from max(linked bugs)
208 sort_age=(proposal.date_review_requested - now).days,
209 project_name=project.name,
210 votes=votes,
211 )
212
213 age = (now - proposal.date_review_requested).days
214
215 if (votes.approve == votes.total(for_approval=True) and
216 (votes.approve >= policy.approvals_to_bypass or
217 votes.approve >= policy.min_approve and
218 age >= policy.days_before_merge)):
219 yield Nag(
220 person='A committer',
221 action="consider to merge the proposal",
222 subject=proposal.web_link,
223 sort_class=SORT_CLASS_MERGE,
224 sort_priority=None, # TODO: get from max(linked bugs)
225 sort_age=(proposal.date_review_requested - now).days,
226 project_name=project.name,
227 votes=votes,
228 )
229 continue
230
231 # Nag about aging merge requests
232 if age >= policy.max_review_age:
233 yield Nag(
234 person='Someone',
235 action="review the merge request",
236 subject=proposal.web_link,
237 sort_class=SORT_CLASS_MERGE,
238 sort_priority=None, # TODO: get from max(linked bugs)
239 sort_age=(proposal.date_review_requested - now).days,
240 project_name=project.name,
241 votes=votes,
242 )
243
244
245def gen_bug_nags(lp, policy, project):
246 now = datetime.datetime.now(tz=UTC)
247 new_threshold = now - datetime.timedelta(days=policy.max_bug_new_age)
248 # Nag about bugs that are "New" for too long
249 logging.debug("Looking at 'new' bugs that are 8 days or older")
250 aging_new_bugs = project.searchTasks(status='New',
251 created_before=new_threshold)
252 for bug_task in aging_new_bugs:
253 yield Nag(
254 person='everyone',
255 action='triage the bug',
256 subject=bug_task.web_link,
257 sort_class=SORT_CLASS_BUG,
258 sort_priority=None, # TODO: convert from importance name
259 sort_age=None,
260 votes=None)
261
262
263def parse_projects_file(filename):
264 """ Parse a file containing name of the projects
265 A line per project
266 """
267 with open(filename, mode='rU') as project_file:
268 content = project_file.read()
269
270 return [name.strip() for name in content.splitlines()]
271
272
273def main():55def main():
274 parser = argparse.ArgumentParser()56 parser = argparse.ArgumentParser()
275 group = parser.add_argument_group(title="debugging")57 group = parser.add_argument_group(title="debugging")
@@ -304,10 +86,7 @@
304 if args.debug:86 if args.debug:
305 logging.basicConfig(level=logging.DEBUG)87 logging.basicConfig(level=logging.DEBUG)
306 # Access Lauchpad object as the current (system) user or anonymously88 # Access Lauchpad object as the current (system) user or anonymously
307 if args.anonymous:89 lp = launchpad_login(args.anonymous, consumer_name, args.service_root)
308 lp = Launchpad.login_anonymously(consumer_name, args.service_root)
309 else:
310 lp = Launchpad.login_with(consumer_name, args.service_root)
31190
312 if not args.project and not args.projects_file:91 if not args.project and not args.projects_file:
313 parser.print_usage()92 parser.print_usage()

Subscribers

People subscribed via source and target branches