setup.py: make libbe._version optional.
[be.git] / libbe / command / merge.py
1 # Copyright (C) 2008-2012 Chris Ball <cjb@laptop.org>
2 #                         Gianluca Montecchi <gian@grys.it>
3 #                         W. Trevor King <wking@tremily.us>
4 #
5 # This file is part of Bugs Everywhere.
6 #
7 # Bugs Everywhere is free software: you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by the Free
9 # Software Foundation, either version 2 of the License, or (at your option) any
10 # later version.
11 #
12 # Bugs Everywhere is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 # FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15 # more details.
16 #
17 # You should have received a copy of the GNU General Public License along with
18 # Bugs Everywhere.  If not, see <http://www.gnu.org/licenses/>.
19
20 import copy
21 import os
22
23 import libbe
24 import libbe.command
25 import libbe.command.util
26
27
28 class Merge (libbe.command.Command):
29     """Merge duplicate bugs
30
31     >>> import sys
32     >>> import libbe.bugdir
33     >>> import libbe.comment
34     >>> bd = libbe.bugdir.SimpleBugDir(memory=False)
35     >>> io = libbe.command.StringInputOutput()
36     >>> io.stdout = sys.stdout
37     >>> ui = libbe.command.UserInterface(io=io)
38     >>> ui.storage_callbacks.set_storage(bd.storage)
39     >>> cmd = Merge(ui=ui)
40
41     >>> a = bd.bug_from_uuid('a')
42     >>> a.comment_root.time = 0
43     >>> dummy = a.new_comment('Testing')
44     >>> dummy.time = 1
45     >>> dummy = dummy.new_reply('Testing...')
46     >>> dummy.time = 2
47     >>> b = bd.bug_from_uuid('b')
48     >>> b.status = 'open'
49     >>> b.comment_root.time = 0
50     >>> dummy = b.new_comment('1 2')
51     >>> dummy.time = 1
52     >>> dummy = dummy.new_reply('1 2 3 4')
53     >>> dummy.time = 2
54
55     >>> ret = ui.run(cmd, args=['/a', '/b'])
56     Merged bugs #abc/a# and #abc/b#
57     >>> bd.flush_reload()
58     >>> a = bd.bug_from_uuid('a')
59     >>> a.load_comments()
60     >>> a_comments = sorted([c for c in a.comments()],
61     ...                     cmp=libbe.comment.cmp_time)
62     >>> mergeA = a_comments[0]
63     >>> mergeA.time = 3
64     >>> print a.string(show_comments=True)
65     ... # doctest: +ELLIPSIS, +REPORT_UDIFF
66               ID : a
67       Short name : abc/a
68         Severity : minor
69           Status : open
70         Assigned : 
71         Reporter : 
72          Creator : John Doe <jdoe@example.com>
73          Created : ...
74     Bug A
75     --------- Comment ---------
76     Name: abc/a/...
77     From: ...
78     Date: ...
79     <BLANKLINE>
80     Testing
81       --------- Comment ---------
82       Name: abc/a/...
83       From: ...
84       Date: ...
85     <BLANKLINE>
86       Testing...
87     --------- Comment ---------
88     Name: abc/a/...
89     From: ...
90     Date: ...
91     <BLANKLINE>
92     Merged from bug #abc/b#
93       --------- Comment ---------
94       Name: abc/a/...
95       From: ...
96       Date: ...
97     <BLANKLINE>
98       1 2
99         --------- Comment ---------
100         Name: abc/a/...
101         From: ...
102         Date: ...
103     <BLANKLINE>
104         1 2 3 4
105     >>> b = bd.bug_from_uuid('b')
106     >>> b.load_comments()
107     >>> b_comments = sorted([c for c in b.comments()],
108     ...                     libbe.comment.cmp_time)
109     >>> mergeB = b_comments[0]
110     >>> mergeB.time = 3
111     >>> print b.string(show_comments=True)
112     ... # doctest: +ELLIPSIS, +REPORT_UDIFF
113               ID : b
114       Short name : abc/b
115         Severity : minor
116           Status : closed
117         Assigned : 
118         Reporter : 
119          Creator : Jane Doe <jdoe@example.com>
120          Created : ...
121     Bug B
122     --------- Comment ---------
123     Name: abc/b/...
124     From: ...
125     Date: ...
126     <BLANKLINE>
127     1 2
128       --------- Comment ---------
129       Name: abc/b/...
130       From: ...
131       Date: ...
132     <BLANKLINE>
133       1 2 3 4
134     --------- Comment ---------
135     Name: abc/b/...
136     From: ...
137     Date: ...
138     <BLANKLINE>
139     Merged into bug #abc/a#
140     >>> print b.status
141     closed
142     >>> ui.cleanup()
143     >>> bd.cleanup()
144     """
145     name = 'merge'
146
147     def __init__(self, *args, **kwargs):
148         libbe.command.Command.__init__(self, *args, **kwargs)
149         self.args.extend([
150                 libbe.command.Argument(
151                     name='bug-id', metavar='BUG-ID', default=None,
152                     completion_callback=libbe.command.util.complete_bug_id),
153                 libbe.command.Argument(
154                     name='bug-id-to-merge', metavar='BUG-ID', default=None,
155                     completion_callback=libbe.command.util.complete_bug_id),
156                 ])
157
158     def _run(self, **params):
159         storage = self._get_storage()
160         bugdirs = self._get_bugdirs()
161         bugdirA,bugA,comment = (
162             libbe.command.util.bugdir_bug_comment_from_user_id(
163                 bugdirs, params['bug-id']))
164         bugA.load_comments()
165         bugdirB,bugB,dummy_comment = (
166             libbe.command.util.bugdir_bug_comment_from_user_id(
167                 bugdirs, params['bug-id-to-merge']))
168         bugB.load_comments()
169         mergeA = bugA.new_comment('Merged from bug #%s#' % bugB.id.long_user())
170         newCommTree = copy.deepcopy(bugB.comment_root)
171         for comment in newCommTree.traverse(): # all descendant comments
172             comment.bug = bugA
173             # uuids must be unique in storage
174             if comment.alt_id == None:
175                 comment.storage = None
176                 comment.alt_id = comment.uuid
177                 comment.storage = storage
178             comment.uuid = libbe.util.id.uuid_gen()
179             comment.save() # force onto disk under bugA
180
181         for comment in newCommTree: # just the child comments
182             mergeA.add_reply(comment, allow_time_inversion=True)
183         bugB.new_comment('Merged into bug #%s#' % bugA.id.long_user())
184         bugB.status = 'closed'
185         print >> self.stdout, 'Merged bugs #%s# and #%s#' \
186             % (bugA.id.user(), bugB.id.user())
187         return 0
188
189     def _long_help(self):
190         return """
191 The second bug (B) is merged into the first (A).  This adds merge
192 comments to both bugs, closes B, and appends B's comment tree to A's
193 merge comment.
194 """