Merge lp:~thumper/launchpad/no-sample-data-doctest into lp:launchpad

Proposed by Tim Penhey on 2010-07-26
Status: Merged
Approved by: Jonathan Lange on 2010-07-26
Approved revision: no longer in the source branch.
Merged at revision: 11243
Proposed branch: lp:~thumper/launchpad/no-sample-data-doctest
Merge into: lp:launchpad
Diff against target: 730 lines (+122/-575)
3 files modified
lib/lp/code/doc/branchmergeproposal.txt (+100/-569)
lib/lp/code/model/tests/test_branchmergeproposal.py (+22/-0)
lib/lp/code/tests/test_doc.py (+0/-6)
To merge this branch: bzr merge lp:~thumper/launchpad/no-sample-data-doctest
Reviewer Review Type Date Requested Status
Jonathan Lange (community) 2010-07-26 Approve on 2010-07-26
Review via email: mp+30913@code.launchpad.net

Commit Message

Make the merge proposal doctest meaningful

Description of the Change

Initially I was only going to change the use of sample data in the doc test.

However on reading the test I realised it was mostly crap. Almost all of what it was talking about was tested in unit tests, and the narrative didn't really tell the reader anything about the concept.

I've pretty much gutted it, and got it to a better state where it could be expanded upon.

I also renamed the file to more match the naming convention of the files for more discover-ability.

To post a comment you must log in.
Jonathan Lange (jml) wrote :

Hey Tim,

Looks great. Two small issues:
  * In the unit tests names, canonical is misspelled as canoncial

  * Is the new doctest file still run during tests? From the diff it looks like it's not.

Otherwise, good to go.

Thanks,
jml

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== renamed file 'lib/lp/code/doc/branch-merge-proposals.txt' => 'lib/lp/code/doc/branchmergeproposal.txt'
2--- lib/lp/code/doc/branch-merge-proposals.txt 2010-04-16 05:51:02 +0000
3+++ lib/lp/code/doc/branchmergeproposal.txt 2010-07-27 01:51:44 +0000
4@@ -1,569 +1,100 @@
5-= Branch merge proposals =
6-
7-Branch merge proposals are a way to show intent of one branch to land
8-code on another branch. The database object is called a merge proposal
9-as it has a source branch and a target branch, but when looking at
10-branch merge proposals through the UI we are looking from a particular
11-context, and as such they are referred to as landing targets or landing
12-candidates.
13-
14- Landing Targets - these are the branches (or the merge proposals) for
15- which the branch that is being looked at is the source branch. It is
16- called that due to the idea that the target branch is where the code
17- intentds to 'land' or 'merge'.
18-
19- Landing Candidates - these are the branches (or the merge proposals)
20- that want to land on the branch being looked at.
21-
22-Branch merge proposals are created by calling the `addLandingTarget`
23-method on a branch. Junk branches cannot have landing targets.
24-
25- * The target branch and prerequisite branch (if it has one) must both
26- have the same product as the source branch.
27-
28- * There must not already exist a branch merge proposal for the source
29- branch and target branch pair.
30-
31-
32-== Registering a landing target ==
33-
34-All merge proposals have to be registered by a Person.
35-
36- >>> from zope.component import getUtility
37- >>> from lp.registry.interfaces.person import IPersonSet
38- >>> from lp.code.interfaces.branchlookup import IBranchLookup
39- >>> person_set = getUtility(IPersonSet)
40- >>> login('test@canonical.com')
41- >>> sample_person = person_set.getByEmail('test@canonical.com')
42- >>> from lp.testing import time_counter
43- >>> now = time_counter()
44-
45-In order to register a merge proposal there has to be a source branch
46-and a target branch.
47-
48- >>> branch_lookup = getUtility(IBranchLookup)
49- >>> source_branch = branch_lookup.getByUniqueName(
50- ... '~name12/gnome-terminal/klingon')
51- >>> target_branch = branch_lookup.getByUniqueName(
52- ... '~name12/gnome-terminal/main')
53- >>> scanned_branch = branch_lookup.getByUniqueName(
54- ... '~name12/gnome-terminal/scanned')
55-
56- >>> merge_proposal = source_branch.addLandingTarget(
57- ... sample_person, target_branch, prerequisite_branch=scanned_branch,
58- ... date_created=now.next())
59-
60-This merge proposal is now listing against both the source and target
61-branches.
62-
63- >>> for proposal in source_branch.landing_targets:
64- ... print proposal.target_branch.unique_name
65- ~name12/gnome-terminal/main
66-
67- >>> for proposal in target_branch.landing_candidates:
68- ... print proposal.source_branch.unique_name
69- ~name12/gnome-terminal/klingon
70-
71-All the code paths through `addLandingTarget` are exercised by the UnitTest
72-lp.code.model.tests.test_branch.BranchAddLandingTarget.
73-
74-In normal project circumstances there will normally be only one landing
75-target registered for any given branch. Sometimes there may be two or more
76-specified but that would be in the situation where a single patch or fix
77-is going to land on multiple supported branches. It is often desirable to
78-see the landing target information even after the merge has taken place.
79-
80-In comparison though, a trunk branch will have a significant number of
81-merge candidates. From the target branch point of view, it is normally
82-only the source branches where the proposals are not in one of the
83-terminal states (merged, rejected, superseded) that are of interest.
84-The landing_candidates only returns unmerged proposals, with the most
85-recent branch merge proposals first.
86-
87- >>> proposal = factory.makeBranchMergeProposal(
88- ... target_branch=target_branch, prerequisite_branch=scanned_branch)
89- >>> proposal = proposal.resubmit(proposal.registrant)
90- >>> proposal.rejectBranch(sample_person, 'some-revision')
91- >>> from lp.code.enums import BranchMergeProposalStatus
92- >>> proposal = factory.makeBranchMergeProposal(
93- ... target_branch=target_branch, prerequisite_branch=scanned_branch,
94- ... set_state=BranchMergeProposalStatus.MERGED)
95-
96- >>> branch = branch_lookup.getByUniqueName(
97- ... '~launchpad/gnome-terminal/launchpad')
98- >>> proposal = branch.addLandingTarget(
99- ... sample_person, target_branch, prerequisite_branch=scanned_branch,
100- ... date_created=now.next())
101-
102- >>> flush_database_updates()
103- >>> from lp.code.model.branchmergeproposal import (
104- ... BranchMergeProposal)
105- >>> # Need to specify orderby - default order is creation date
106- >>> # and objects created in the same transaction have identical
107- >>> # creation dates
108- >>> for proposal in BranchMergeProposal.selectBy(
109- ... target_branch=target_branch, orderBy=['id']):
110- ... print proposal.queue_status.title
111- Work in progress
112- Superseded
113- Rejected
114- Merged
115- Work in progress
116-
117- >>> for proposal in target_branch.landing_candidates:
118- ... print proposal.queue_status.title
119- Work in progress
120- Work in progress
121-
122- >>> for proposal in BranchMergeProposal.selectBy(
123- ... prerequisite_branch=scanned_branch, orderBy=['id']):
124- ... print proposal.queue_status.title
125- Work in progress
126- Superseded
127- Rejected
128- Merged
129- Work in progress
130-
131- >>> for proposal in scanned_branch.dependent_branches:
132- ... print proposal.queue_status.title
133- Work in progress
134- Work in progress
135-
136-
137-== Merge proposal states ==
138-
139-A branch merge proposal can be in one of several states:
140-
141- >>> for state in BranchMergeProposalStatus:
142- ... print state.title
143- Work in progress
144- Needs review
145- Approved
146- Rejected
147- Merged
148- Code failed to merge
149- Queued
150- Superseded
151-
152-When a merge proposal is initially created, it is in the "Work in
153-progress" state. The only state that is a final state is "Merged".
154-
155- >>> source_branch = factory.makeProductBranch(owner=sample_person)
156- >>> # Needs to be the same product to merge into.
157- >>> target_branch = factory.makeProductBranch(
158- ... owner=sample_person, product=source_branch.product)
159- >>> proposal = source_branch.addLandingTarget(
160- ... sample_person, target_branch, date_created=now.next())
161- >>> print proposal.queue_status.title
162- Work in progress
163-
164-Adjusting the state is done through methods on the merge proposal.
165-
166- >>> print proposal.date_review_requested
167- None
168- >>> proposal.requestReview()
169- >>> print proposal.queue_status.title
170- Needs review
171-
172-When requesting a review, a timestamp is also recorded.
173-
174- >>> proposal.date_review_requested is not None
175- True
176-
177-Directly setting the queue_status is not possible.
178-
179- >>> proposal.queue_status = BranchMergeProposalStatus.CODE_APPROVED
180- Traceback (most recent call last):
181- ...
182- ForbiddenAttribute: ('queue_status', <BranchMergeProposal at ...>)
183-
184-
185-=== Approving or rejecting code ===
186-
187-Once code has been reviewed the result of the code review can be
188-recorded either as approved or not approved.
189-
190-There is a limited number of people that are able to make code as
191-approved or not. By default the owner of the target branch can sign-off
192-code. If the target branch has specified a review team, then we use that
193-to control who can sign-off code.
194-
195-Initially both the reviewer and date_reviewed for a merge proposal
196-are not set.
197-
198- >>> print proposal.reviewer
199- None
200- >>> print proposal.date_reviewed
201- None
202-
203-Since Sample Person is the owner of the target branch they can authorise
204-their own code.
205-
206-When approving code the revision id is recorded as the last approved
207-revision. This is to record that the reviewer has approved up to a
208-particular revision, and that subsequent revsions added by the branch
209-author have not been approved.
210-
211- >>> def add_some_revisions_to_branch(branch, factory, branch_lookup):
212- ... # Revisions can only be added by the scanner, so switch dbuser.
213- ... from canonical.database.sqlbase import commit
214- ... from canonical.testing import LaunchpadZopelessLayer
215- ... from canonical.config import config
216- ... branch_unique_name = branch.unique_name
217- ... launchpad_dbuser = config.launchpad.dbuser
218- ... commit()
219- ... LaunchpadZopelessLayer.switchDbUser(config.branchscanner.dbuser)
220- ... factory.makeRevisionsForBranch(branch)
221- ... commit()
222- ... LaunchpadZopelessLayer.switchDbUser(launchpad_dbuser)
223- ... return branch_lookup.getByUniqueName(branch_unique_name)
224- >>> source_branch = add_some_revisions_to_branch(
225- ... source_branch, factory, branch_lookup)
226- >>> proposal = source_branch.landing_targets[0]
227- >>> source_branch.revision_count
228- 5
229-
230- >>> # Reget the sample person as we shouldn't be reusing objects across
231- >>> # transaction boundaries.
232- >>> sample_person = person_set.getByEmail('test@canonical.com')
233- >>> tip = source_branch.getTipRevision()
234- >>> proposal.approveBranch(sample_person, tip.revision_id)
235- >>> print proposal.reviewer.displayname
236- Sample Person
237- >>> proposal.date_reviewed is not None
238- True
239- >>> print proposal.queue_status.title
240- Approved
241- >>> proposal.reviewed_revision_id == tip.revision_id
242- True
243-
244-Code that has been accepted can also be set to rejected. Rejected
245-branches also record the revision that was rejected.
246-
247- >>> proposal.rejectBranch(sample_person, tip.revision_id)
248- >>> print proposal.queue_status.title
249- Rejected
250- >>> proposal.reviewed_revision_id == tip.revision_id
251- True
252-
253-Proposals that are `Approved` can be moved back into the
254-`Needs review` state.
255-
256- >>> proposal = proposal.resubmit(sample_person)
257- >>> proposal.approveBranch(sample_person, tip.revision_id)
258- >>> print proposal.queue_status.title
259- Approved
260- >>> proposal.requestReview()
261- >>> print proposal.queue_status.title
262- Needs review
263-
264-Branches that had been approved that have subsequently been set back into the
265-needs review state have their reviewed revision cleared.
266-
267- >>> print proposal.reviewed_revision_id
268- None
269-
270-If the target branch has specified a specific reviewer, then either the owner
271-of the target branch or the reviewer is able to approve or reject the
272-proposal.
273-
274- >>> eric_the_reviewer = factory.makePerson(name="eric-reviewer")
275- >>> target_branch = proposal.target_branch
276- >>> target_branch.reviewer = eric_the_reviewer
277- >>> proposal.rejectBranch(sample_person, tip.revision_id)
278- >>> print proposal.queue_status.title
279- Rejected
280- >>> proposal.approveBranch(eric_the_reviewer, tip.revision_id)
281- >>> print proposal.queue_status.title
282- Approved
283-
284-
285-== Queueing up a branch that has been approved ==
286-
287-A branch proposal to merge that has been approved can be added to the
288-merge queue for the target branch. The proposal to merge will be given
289-a queue position which has no significance other than defining the
290-order in the queue. There can be no assumptions on the actual value
291-of the queue position as any integer value is valid, and a smaller
292-integer value comes before a larger integer value in the queue.
293-
294-When a proposal is queued, the person that queues up the branch is
295-recorded, along with the revision_id of the tip revision. This revision_id
296-is the revision that will be merged into the target branch.
297-
298- >>> proposal.enqueue(sample_person, tip.revision_id)
299- >>> print proposal.queue_status.title
300- Queued
301- >>> print proposal.queuer.displayname
302- Sample Person
303- >>> proposal.queued_revision_id == tip.revision_id
304- True
305- >>> proposal.queue_position is not None
306- True
307-
308-
309-== Removing a branch from the merge queue ==
310-
311-A branch proposal to merge that has been queued can also be removed
312-from the queue. Doing so moves the state of the proposal back to
313-Approved.
314-
315- >>> proposal.dequeue()
316- >>> from canonical.launchpad.ftests import syncUpdate
317- >>> syncUpdate(proposal)
318- >>> print proposal.queue_status.title
319- Approved
320- >>> print proposal.queue_position
321- None
322- >>> print proposal.queuer
323- None
324-
325-
326-== Transitioning through code approved when queueing ==
327-
328-If the proposal to merge was not in the approved state before attempting
329-to queue the branch, then an implicit approval is attempted. If the user
330-queueing the branch is not a reviewer, an exception is raised.
331-
332- >>> proposal.requestReview()
333- >>> print proposal.queue_status.title
334- Needs review
335-
336- >>> random_user = factory.makePerson()
337- >>> proposal.enqueue(random_user, tip.revision_id)
338- Traceback (most recent call last):
339- UserNotBranchReviewer
340-
341- >>> proposal.enqueue(eric_the_reviewer, tip.revision_id)
342- >>> print proposal.queue_status.title
343- Queued
344- >>> print proposal.queuer.name
345- eric-reviewer
346- >>> print proposal.reviewer.name
347- eric-reviewer
348-
349-
350-== Marking as failed to merge ==
351-
352-Anyone that has rights to edit the merge proposal can say that the
353-merge failed.
354-
355- >>> proposal.setStatus(BranchMergeProposalStatus.MERGE_FAILED)
356- >>> print proposal.queue_status.title
357- Code failed to merge
358-
359-Proposals that are in the "Code failed to merge" state can be set back
360-to any other state.
361-
362- >>> proposal.setAsWorkInProgress()
363- >>> print proposal.queue_status.title
364- Work in progress
365-
366-
367-== Getting a list of previous merge targets ==
368-
369-In order to make it easier to propose branches to merge on similar
370-branches, there is a method on the branch set that returns the branches that a
371-user has previously targeted for merging.
372-
373- >>> collection = branch.target.collection.targetedBy(sample_person)
374- >>> collection = collection.visibleByUser(sample_person)
375- >>> branches = collection.getBranches().config(distinct=True)
376- >>> for branch in branches:
377- ... print branch.unique_name
378- ~name12/gnome-terminal/main
379-
380-
381-== Marking as merged ==
382-
383-A merge proposal may be marked as merged by calling the `markAsMerged` method
384-on the `BranchMergeProposal`.
385-
386-If a revision number is supplied, the method looks for a `BranchRevision` in
387-the target branch that matches. If one is found then the revision date from
388-the associated `Revision` is used as the merge date. This allows branches
389-that are not necessarily available to launchpad to manually mark the merge
390-proposals as merged.
391-
392-The user that marked the branch as merged can also be recorded.
393-
394- >>> branch = branch_lookup.getByUniqueName(
395- ... '~launchpad/gnome-terminal/launchpad')
396- >>> merge_proposal = branch.landing_targets[0]
397- >>> reporter = person_set.getByName('mark')
398- >>> merge_proposal.markAsMerged(1234, now.next(), reporter)
399- >>> merge_proposal.merged_revno
400- 1234
401- >>> print merge_proposal.merge_reporter.displayname
402- Mark Shuttleworth
403- >>> print merge_proposal.queue_status.title
404- Merged
405- >>> flush_database_updates()
406-
407-The merged proposal is no longer listed amongst the target's candidates.
408-
409- >>> print merge_proposal.source_branch.unique_name
410- ~launchpad/gnome-terminal/launchpad
411- >>> for proposal in merge_proposal.target_branch.landing_candidates:
412- ... print proposal.source_branch.unique_name
413- ~name12/gnome-terminal/klingon
414-
415-But the merge proposal is still listed in the source's targets.
416-
417- >>> for proposal in merge_proposal.source_branch.landing_targets:
418- ... print proposal.target_branch.unique_name
419- ~name12/gnome-terminal/main
420-
421-The firefox branches are the branches that are attached to a product
422-and have revisions (well, a revision).
423-
424- >>> source_branch = branch_lookup.getByUniqueName(
425- ... '~mark/firefox/release-0.9')
426- >>> target_branch = branch_lookup.getByUniqueName(
427- ... '~mark/firefox/release-0.9.2')
428- >>> merge_proposal = source_branch.addLandingTarget(
429- ... sample_person, target_branch, date_created=now.next())
430-
431-The target branch has a revision 1, so the merge time is taken form
432-that revision.
433-
434- >>> history = list(target_branch.revision_history)
435- >>> branch_revision = history[0]
436- >>> branch_revision.sequence
437- 1
438- >>> print branch_revision.revision.revision_date
439- 2005-03-09 15:40:00+00:00
440-
441- >>> merge_proposal.markAsMerged(1, now.next(), reporter)
442- >>> print merge_proposal.date_merged
443- 2005-03-09 15:40:00+00:00
444- >>> print merge_proposal.merge_reporter.displayname
445- Mark Shuttleworth
446-
447-
448-== Interfaces ==
449-
450-The BranchMergeProposal must implement the IBranchMergeProposal interface.
451-
452- >>> from canonical.launchpad.webapp.testing import verifyObject
453- >>> from lp.code.interfaces.branchmergeproposal import (
454- ... IBranchMergeProposal)
455- >>> verifyObject(IBranchMergeProposal, merge_proposal)
456- True
457-
458-
459-== Canonical URL ==
460-
461-The URL of a branch merge proposal is based on the source branch.
462-In order to keep the URL managable, the database ID of the merge
463-proposal is used.
464-
465- >>> login('test@canonical.com')
466- >>> from canonical.launchpad.webapp import canonical_url
467- >>> merge_proposal = source_branch.landing_targets[0]
468- >>> url = canonical_url(merge_proposal)
469- >>> url[url.rfind('/')+1:] == str(merge_proposal.id)
470- True
471- >>> print url
472- http://code.launchpad.dev/~mark/firefox/release-0.9/+merge/...
473-
474-
475-== Deleting merge proposals ==
476-
477-Deleting merge proposals is done using the deleteProposal() method on
478-the merge proposal itself. The `deleteProposal` method is protected by
479-launchpad.Edit, which restricts access to the owner of the source
480-branch, the owner of the target branch, or the registrant (and LP
481-admins).
482-
483- >>> source_branch.landing_targets.count()
484- 1
485- >>> merge_proposal = source_branch.landing_targets[0]
486- >>> login('no-priv@canonical.com')
487- >>> merge_proposal.deleteProposal()
488- Traceback (most recent call last):
489- ...
490- Unauthorized: (<BranchMergeProposal ...>,
491- 'deleteProposal', 'launchpad.Edit')
492-
493-Sample Person is the registrant, so they can delete it.
494-
495- >>> login('test@canonical.com')
496- >>> merge_proposal.deleteProposal()
497- >>> source_branch.landing_targets.count()
498- 0
499-
500-
501-== Resubmitting merge proposals ==
502-
503-If a proposal has been rejected, the proposal can be resubmitted
504-(normally after the branch author has fixed the problems). When a
505-proposal is resubmitted the original proposal is marked as SUPERSEDED
506-and a new proposal is created which has the same details as the
507-original. The new proposal has an attribute called
508-`supersedes` which is the proposal it superseded (i.e. the
509-original proposal). The original proposal also has an attribute
510-`superseded_by` which is the proposal that supersedes it (i.e. the
511-new proposal).
512-
513- >>> proposal = factory.makeBranchMergeProposal(registrant=sample_person)
514- >>> proposal.rejectBranch(proposal.target_branch.owner, 'some-revision')
515- >>> print proposal.queue_status.title
516- Rejected
517-
518- >>> new_proposal = proposal.resubmit(sample_person)
519- >>> print new_proposal.queue_status.title
520- Needs review
521- >>> new_proposal == proposal
522- False
523- >>> new_proposal.supersedes == proposal
524- True
525- >>> flush_database_updates()
526- >>> proposal.superseded_by == new_proposal
527- True
528-
529-If the new proposal is deleted, the old proposal stays in the
530-superseded state, but no longer has a `superseded_by` proposal.
531-
532- >>> new_proposal.deleteProposal()
533- >>> print proposal.queue_status.title
534- Superseded
535- >>> print proposal.superseded_by
536- None
537-
538-If the old proposal is deleted, the new proposal no longer shows
539-the supersedes.
540-
541- >>> proposal = factory.makeBranchMergeProposal(registrant=sample_person)
542- >>> new_proposal = proposal.resubmit(sample_person)
543- >>> new_proposal.supersedes == proposal
544- True
545-
546- >>> flush_database_updates()
547- >>> proposal.deleteProposal()
548- >>> print new_proposal.supersedes
549- None
550-
551-If there is a chain of superseded proposals, and the one in the middle
552-is deleted, then the chain is just made shorter.
553-
554- >>> original = factory.makeBranchMergeProposal(registrant=sample_person)
555- >>> middle = original.resubmit(sample_person)
556- >>> last = middle.resubmit(sample_person)
557- >>> flush_database_updates()
558-
559- >>> original.superseded_by == middle
560- True
561- >>> middle.supersedes == original
562- True
563- >>> middle.superseded_by == last
564- True
565- >>> last.supersedes == middle
566- True
567-
568- >>> middle.deleteProposal()
569-
570- >>> original.superseded_by == last
571- True
572- >>> last.supersedes == original
573- True
574+Branch merge proposals
575+======================
576+
577+The purpose of branch merge proposals are to indicate that the one branch
578+should be merged into another branch.
579+
580+The branch to be merged is referred to as the "source branch", and the branch
581+that is being merged into is referred to as the "target branch".
582+
583+In the early phases of development of this feature there was often a mixing of
584+terms where the term "merge proposal" was used interchangably with "code
585+review" which caused some confusion.
586+
587+- **Merge Proposal:** indicates that one branch should be merged with another.
588+- **Code Review:** a discussion that takes place about the changes that would
589+ occur should the proposed merge happen
590+
591+A merge proposal may have a code reivew, but a code review always happens with
592+respect to a merge proposal.
593+
594+
595+Other Terms
596+-----------
597+
598+Landing Targets
599+ These are the merge proposals related to the branch for which the branch
600+ that is being looked at is the source branch. It is called that due to the
601+ idea that the target branch is where the code intentds to 'land' or 'merge'.
602+
603+Landing Candidates
604+ These are the merge proposals that indicate the intent to merge with the
605+ branch being looked at.
606+
607+
608+Creating a Merge Proposal
609+-------------------------
610+
611+All merge proposals are created from the source branch using a method called
612+``addLandingTarget``.
613+
614+ >>> fooix = factory.makeProduct(name='fooix')
615+ >>> source_branch = factory.makeProductBranch(product=fooix)
616+ >>> target_branch = factory.makeProductBranch(product=fooix)
617+ >>> merge_proposal = source_branch.addLandingTarget(
618+ ... registrant=source_branch.owner,
619+ ... target_branch=target_branch)
620+
621+The bare minimum that needs to be specified is the person that is proposing
622+the merge, the ``registrant``, and the branch that the registrant wants the
623+branch to be merged into, the ``target_branch``.
624+
625+There are other optional parameters to initialize other merge proposal
626+attributes such as the description, commit message or requested reviewers.
627+
628+It is considered good form to set at least one of the commit message or
629+description. For very simple branches a commit message might be enough, but
630+for non-trivial branches a description is often needed to describe the intent
631+of the changes.
632+
633+
634+States of a Merge Proposal
635+--------------------------
636+
637+During the life time of a merge proposal it will go through many states.
638+
639+Work in Progress
640+ The source branch is intended to be merged into the target, but the work has
641+ not yet been fully completed.
642+
643+Needs Review
644+ The work has been completed to the satisfaction of the branch owner, or at
645+ least some form of review of the change is being requested.
646+
647+Approved
648+ The reviewer is happy with the change.
649+
650+Rejected
651+ The reviews is not happy with the change.
652+
653+Merged
654+ The source branch has been merged into the target. The branch scanner also
655+ sets the ``merge_revno`` of the merge proposal to indicate which revision
656+ number on the target branch has this merge in it.
657+
658+Superseded
659+ The intent of superseded proposals has changed somewhat over time, and needs
660+ some rework (bugs 383352, 397444, 400030, 488544)
661+
662+
663+There are also some other states that are not yet in general use:
664+
665+Queued
666+ The merge proposal is queued for merging. The proposal will be part of some
667+ merge queue which a person or script (like tarmac) will process and do the
668+ merges.
669+
670+Merge Failed
671+ A script tried to merge the branch but it either failed to merge cleanly
672+ (had conflicts) or failed some tests as defined by the script. There is the
673+ facility to add a log file containing the failures here.
674
675=== renamed file 'lib/lp/code/model/tests/test_branchmergeproposals.py' => 'lib/lp/code/model/tests/test_branchmergeproposal.py'
676--- lib/lp/code/model/tests/test_branchmergeproposals.py 2010-05-27 01:34:14 +0000
677+++ lib/lp/code/model/tests/test_branchmergeproposal.py 2010-07-27 01:51:44 +0000
678@@ -25,6 +25,7 @@
679
680 from canonical.launchpad.interfaces import IPrivacy
681 from canonical.launchpad.interfaces.message import IMessageJob
682+from canonical.launchpad.webapp import canonical_url
683 from canonical.launchpad.webapp.testing import verifyObject
684 from lp.code.errors import (
685 BadStateTransition, WrongBranchMergeProposal)
686@@ -64,6 +65,27 @@
687 verifyObject(IBranchMergeProposal, bmp)
688
689
690+class TestBranchMergeProposalCanonicalUrl(TestCaseWithFactory):
691+ """Tests canonical_url for merge proposals."""
692+
693+ layer = DatabaseFunctionalLayer
694+
695+ def test_BranchMergeProposal_canonical_url_base(self):
696+ # The URL for a merge proposal starts with the source branch.
697+ bmp = self.factory.makeBranchMergeProposal()
698+ url = canonical_url(bmp)
699+ source_branch_url = canonical_url(bmp.source_branch)
700+ self.assertTrue(url.startswith(source_branch_url))
701+
702+ def test_BranchMergeProposal_canonical_url_rest(self):
703+ # The rest of the URL for a merge proposal is +merge followed by the db id.
704+ bmp = self.factory.makeBranchMergeProposal()
705+ url = canonical_url(bmp)
706+ source_branch_url = canonical_url(bmp.source_branch)
707+ rest = url[len(source_branch_url):]
708+ self.assertEqual('/+merge/%s' % bmp.id, rest)
709+
710+
711 class TestBranchMergeProposalPrivacy(TestCaseWithFactory):
712 """Ensure that BranchMergeProposal implements privacy."""
713
714
715=== modified file 'lib/lp/code/tests/test_doc.py'
716--- lib/lp/code/tests/test_doc.py 2010-01-13 02:13:19 +0000
717+++ lib/lp/code/tests/test_doc.py 2010-07-27 01:51:44 +0000
718@@ -45,12 +45,6 @@
719 tearDown=zopelessLaunchpadSecurityTearDown,
720 layer=LaunchpadZopelessLayer,
721 ),
722- 'branch-merge-proposals.txt': LayeredDocFileSuite(
723- '../doc/branch-merge-proposals.txt',
724- setUp=zopelessLaunchpadSecuritySetUp,
725- tearDown=zopelessLaunchpadSecurityTearDown,
726- layer=LaunchpadZopelessLayer,
727- ),
728 'revision.txt': LayeredDocFileSuite(
729 '../doc/revision.txt',
730 setUp=branchscannerSetUp, tearDown=tearDown,