public
Description: A distributed issue tracking system based on Git repositories, written in Python
Homepage: http://www.newartisans.com
Clone URL: git://github.com/jwiegley/git-issues.git
Click here to lend your support to: git-issues and make a donation at www.pledgie.com !
git-issues / git-issues
5f44f6e0 » jwiegley 2008-05-12 Initial commit 1 #!/usr/bin/env python
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 2 # coding: utf-8
5f44f6e0 » jwiegley 2008-05-12 Initial commit 3
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 4 # git-issue, version 0.3
5f44f6e0 » jwiegley 2008-05-12 Initial commit 5 #
6 # by John Wiegley <johnw@newartisans.com>
7
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 8 # TODO: (until I can add these bugs to the repo itself!)
9 #
10 # 1. use utf-8 throughout
11 # 2. use -z flag for ls-tree
12 # 3. use UTC throughout
13
bcd9ce29 » edrik 2008-08-20 Cannot import readlink on w... 14 import platform
15
c0607c42 » Giulio Eulisse 2008-05-22 Allows git-issues usage as ... 16 from os.path import split, join, exists, dirname
bcd9ce29 » edrik 2008-08-20 Cannot import readlink on w... 17 from os import getcwd, makedirs, execv
c0607c42 » Giulio Eulisse 2008-05-22 Allows git-issues usage as ... 18 from shutil import copy
19 from sys import argv
20
bcd9ce29 » edrik 2008-08-20 Cannot import readlink on w... 21 if platform.system() == "Windows":
d10f3556 » sbohrer 2008-06-30 Fix indentation 22 resolvedLink = None
bcd9ce29 » edrik 2008-08-20 Cannot import readlink on w... 23 else:
24 from os import readlink
25 try:
26 resolvedLink = readlink(__file__)
27 except:
28 resolvedLink = None
29
30 if resolvedLink and resolvedLink[0] != "/":
31 resolvedLink = join(dirname(__file__), resolvedLink)
32 if resolvedLink:
33 #print "Symlink found, using %s instead" % resolvedLink
34 execv(resolvedLink, [resolvedLink] + argv[1:])
c0607c42 » Giulio Eulisse 2008-05-22 Allows git-issues usage as ... 35
36 path = getcwd()
37
bcd9ce29 » edrik 2008-08-20 Cannot import readlink on w... 38 if ".gitissues" not in __file__:
c0607c42 » Giulio Eulisse 2008-05-22 Allows git-issues usage as ... 39 while not exists(join(path,".gitissues")):
40 path,extra = split(path)
41 if not extra:
42 break
43 issuesExec = join(path,".gitissues/git-issues")
44 if exists(issuesExec):
45 #print "git-issues found in %s. Using it in place of the one in %s" % (issuesExec, __file__)
46 execv(issuesExec, [issuesExec]+ argv[1:])
47 assert ("This should never be called" and False)
e5a93a93 » sbohrer 2008-06-29 Pick editor from $VISUAL, $... 48
5f44f6e0 » jwiegley 2008-05-12 Initial commit 49 import sys
50 import os
51 import re
52 import optparse
53
f3268125 » jwiegley 2008-05-14 Added support for iteration. 54 import gitshelve
55
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 56 try:
57 from cStringIO import StringIO
58 except:
59 from StringIO import StringIO
60
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 61 import cPickle
62
5f44f6e0 » jwiegley 2008-05-12 Initial commit 63 from datetime import datetime
64 from subprocess import Popen, PIPE
47f1c6f9 » ktf 2008-06-02 Helper command "close" adde... 65 from os.path import isdir, isfile, join, basename
d0bafd0b » ktf 2008-06-03 `git-issues edit` command a... 66 from tempfile import mkstemp
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 67 ######################################################################
68
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 69 iso_fmt = "%Y%m%dT%H%M%S"
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 70 options = None
18f58232 » jwiegley 2008-05-17 Updated the cache_version. 71 cache_version = 11
5f44f6e0 » jwiegley 2008-05-12 Initial commit 72
73 ######################################################################
74
cab44d22 » jwiegley 2008-05-14 Added the initial infra-str... 75 # You may wonder what dirtiness means below. Here's the deal:
76 #
77 # An object is "self-dirty" if it itself has been changed, but possibly none
78 # of its children. An object is "dirty" if its children have been changed,
79 # but possibly not itself. Any dirtiness is cause for rewriting the object
80 # cache; only self-dirtiness of specific objects will cause the repository to
81 # be updated.
82
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 83 class Person:
84 def __init__(self, name, email):
85 self.name = name
86 self.email = email
87
88 def __str__(self):
89 return "%s <%s>" % (self.name, self.email)
90
cab44d22 » jwiegley 2008-05-14 Added the initial infra-str... 91 class Comment:
92 def __init__(self, issue, author, comment):
a71d32ca » ktf 2008-06-22 Initial comment support. 93 self.name = None
cab44d22 » jwiegley 2008-05-14 Added the initial infra-str... 94 self.issue = issue
95 self.author = author
96 self.comment = comment
97 self.created = datetime.now()
98 self.modified = None
99 self.self_dirty = True
100 self.attachments = [] # records filename and blob
a71d32ca » ktf 2008-06-22 Initial comment support. 101 self.issue.comments[self.get_name()] = self # register into issue
cab44d22 » jwiegley 2008-05-14 Added the initial infra-str... 102
103 def mark_dirty(self):
104 self.modified = datetime.now()
105 self.self_dirty = True
f3268125 » jwiegley 2008-05-14 Added support for iteration. 106 self.issue.mark_dirty(self_dirty = False)
cab44d22 » jwiegley 2008-05-14 Added the initial infra-str... 107
108 def __getstate__(self):
109 odict = self.__dict__.copy() # copy the dict since we change it
110 del odict['self_dirty'] # remove self dirty flag
111 return odict
112
a71d32ca » ktf 2008-06-22 Initial comment support. 113 def __setstate__(self, dict):
cab44d22 » jwiegley 2008-05-14 Added the initial infra-str... 114 self.__dict__.update(dict) # update attributes
115 self.self_dirty = False
116
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 117 class Issue:
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 118 def __init__(self, issueSet, author, title,
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 119 summary = None,
120 description = None,
121 reporters = [],
122 owners = [],
123 assigned = None,
124 carbons = [],
125 status = "new",
126 resolution = None,
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 127 issue_type = "defect",
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 128 components = [],
129 version = None,
130 milestone = None,
131 severity = "major",
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 132 priority = "medium",
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 133 tags = []):
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 134 self.issueSet = issueSet
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 135 self.name = None
136 self.author = author
137 self.title = title
138 self.summary = summary
139 self.description = description
140 self.reporters = reporters
141 self.owners = owners
142 self.assigned = assigned
143 self.carbons = carbons
144 self.status = status
145 self.resolution = resolution
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 146 self.issue_type = issue_type
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 147 self.components = components
148 self.version = version
149 self.milestone = milestone
150 self.severity = severity
151 self.priority = priority
152 self.tags = tags
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 153 self.created = datetime.now()
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 154 self.modified = None
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 155 self.changes = {}
cab44d22 » jwiegley 2008-05-14 Added the initial infra-str... 156 self.dirty = False
157 self.self_dirty = True
158 self.comments = {}
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 159
cab44d22 » jwiegley 2008-05-14 Added the initial infra-str... 160 def mark_dirty(self, self_dirty):
161 self.dirty = True
162 if self_dirty:
163 self.self_dirty = True
164 self.modified = datetime.now()
f3268125 » jwiegley 2008-05-14 Added support for iteration. 165 self.issueSet.mark_dirty(self_dirty = False)
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 166
3bb55535 » jwiegley 2008-05-14 git-issues is now using git... 167 def get_name(self):
168 assert False
169
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 170 def note_change(self, field, before, after):
171 if self.changes.has_key(field):
172 data = self.changes[field]
173 data[1] = after
174 else:
175 data = [before, after]
176 self.changes[field] = data
177
178 def set_author(self, author):
179 self.note_change('author', self.author, author)
180 self.author = author
181
182 def set_title(self, title):
183 self.note_change('title', self.title, title)
184 self.title = title
185
186 def set_summary(self, summary):
187 self.note_change('summary', self.summary, summary)
188 self.summary = summary
189
190 def set_description(self, description):
191 self.note_change('description', self.description, description)
192 self.description = description
193
194 def set_reporters(self, reporters):
195 self.note_change('reporters', self.reporters, reporters)
196 self.reporters = reporters
197
198 def set_owners(self, owners):
199 self.note_change('owners', self.owners, owners)
200 self.owners = owners
201
202 def set_assigned(self, assigned):
203 self.note_change('assigned', self.assigned, assigned)
204 self.assigned = assigned
205
206 def set_carbons(self, carbons):
207 self.note_change('carbons', self.carbons, carbons)
208 self.carbons = carbons
209
210 def set_status(self, status):
211 self.note_change('status', self.status, status)
212 self.status = status
213
214 def set_resolution(self, resolution):
215 self.note_change('resolution', self.resolution, resolution)
216 self.resolution = resolution
217
218 def set_issue_type(self, issue_type):
219 self.note_change('type', self.issue_type, issue_type)
220 self.issue_type = issue_type
221
222 def set_components(self, components):
223 self.note_change('components', self.components, components)
224 self.components = components
225
226 def set_version(self, version):
227 self.note_change('version', self.version, version)
228 self.version = version
229
230 def set_milestone(self, milestone):
231 self.note_change('milestone', self.milestone, milestone)
232 self.milestone = milestone
233
234 def set_severity(self, severity):
235 self.note_change('severity', self.severity, severity)
236 self.severity = severity
237
238 def set_priority(self, priority):
239 self.note_change('priority', self.priority, priority)
240 self.priority = priority
241
242 def set_tags(self, tags):
243 self.note_change('tags', self.tags, tags)
244 self.tags = tags
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 245
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 246 def __getstate__(self):
247 odict = self.__dict__.copy() # copy the dict since we change it
248 del odict['changes'] # remove change log
249 del odict['dirty'] # remove dirty flag
cab44d22 » jwiegley 2008-05-14 Added the initial infra-str... 250 del odict['self_dirty'] # remove self dirty flag
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 251 return odict
252
253 def __setstate__(self,dict):
254 self.__dict__.update(dict) # update attributes
255 self.changes = {}
cab44d22 » jwiegley 2008-05-14 Added the initial infra-str... 256 self.dirty = False
257 self.self_dirty = False
dc1b7f66 » jwiegley 2008-05-12 Re-implemented the 'show' c... 258
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 259 class IssueSet:
260 """An IssueSet refers to a group of issues. There is always at least one
261 IssueSet that refers to all of the issues which exist in a repository.
262 Other IssueSet's can be generated from that one as "views" or queries into
263 that data.
264
265 In essence, it contains both a set of Issue's which can be looked up by
266 their unique identifier, and also certain global definition, like the
267 allowable components, etc."""
f3268125 » jwiegley 2008-05-14 Added support for iteration. 268 def __init__(self, shelf):
269 self.shelf = shelf
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 270 self.statuses = []
271 self.resolutions = []
272 self.issue_types = []
273 self.components = []
274 self.versions = []
275 self.milestones = []
276 self.severities = []
277 self.priorities = []
278 self.dirty = False
cab44d22 » jwiegley 2008-05-14 Added the initial infra-str... 279 self.self_dirty = True
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 280 self.cache_version = cache_version
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 281 self.created = datetime.now()
cab44d22 » jwiegley 2008-05-14 Added the initial infra-str... 282 self.modified = None
283
284 def mark_dirty(self, self_dirty):
285 self.dirty = True
286 if self_dirty:
287 self.modified = datetime.now()
288 self.self_dirty = True
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 289
f3268125 » jwiegley 2008-05-14 Added support for iteration. 290 def current_author(self):
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 291 assert False
292
f3268125 » jwiegley 2008-05-14 Added support for iteration. 293 def allocate_issue(self, title):
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 294 assert False
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 295
f3268125 » jwiegley 2008-05-14 Added support for iteration. 296 def new_issue(self, title):
297 issue = self.allocate_issue(title)
298 self.add_issue(issue)
299 return issue
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 300
1efa9acd » ktf 2008-06-20 Support for managing comments. 301 def new_comment(self, issue, text):
302 comment = self.allocate_comment(issue, text)
303 self.add_comment(comment)
304 return comment
305
306 def comment_path(self, comment):
307 name = comment.issue.get_name()
258379ab » ktf 2008-06-22 Now it actually works to st... 308 return "%s/%s/comment_%s_%s_%s.xml" %(name[:2],
1efa9acd » ktf 2008-06-20 Support for managing comments. 309 name[2:],
258379ab » ktf 2008-06-22 Now it actually works to st... 310 comment.name,
1efa9acd » ktf 2008-06-20 Support for managing comments. 311 datetime.now().isoformat(),
312 comment.comment)
f3268125 » jwiegley 2008-05-14 Added support for iteration. 313 def issue_path(self, issue):
3bb55535 » jwiegley 2008-05-14 git-issues is now using git... 314 name = issue.get_name()
315 return '%s/%s/issue.xml' % (name[:2], name[2:])
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 316
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 317 def add_issue(self, issue):
f3268125 » jwiegley 2008-05-14 Added support for iteration. 318 self.shelf[self.issue_path(issue)] = issue
319 self.mark_dirty(self_dirty = False)
320
1efa9acd » ktf 2008-06-20 Support for managing comments. 321 def add_comment(self, comment):
322 self.shelf[self.comment_path(comment)] = comment
323 self.mark_dirty(self_dirty = False)
324
a71d32ca » ktf 2008-06-22 Initial comment support. 325 def get_comment(self, idx_or_partial_hash):
326 comment = None
327 try:
328 idx = int(idx_or_partial_hash) - 1
329 comment = self.shelf[self.shelf.keys()[idx]]
330 except:
258379ab » ktf 2008-06-22 Now it actually works to st... 331 print [key for key in self.shelf.iterkeys()]
332 def getCommentId(x):
333 if not 'comment_' in x:
334 return ""
335 x = x.split('comment_')[1]
336 x = x.split('_')[0]
337 return x
338 clean = lambda x: getCommentId(x)
a71d32ca » ktf 2008-06-22 Initial comment support. 339 matching = [(clean(key), key) for key in self.shelf.iterkeys()
340 if clean(key).startswith(idx_or_partial_hash) and not "issue.xml" in clean(key)]
341 if len(matching) == 0:
342 pass
343 elif len(matching) == 1:
344 comment = self.shelf[matching[0][1]]
345 else:
346 print ("Ambiguous hash matches:\n" +
347 '\t\n'.join(a[0] for a in matching))
348 if not comment:
349 raise Exception("There is no issue matching the identifier '%s'.\n" %
350 idx_or_partial_hash)
351 return comment
352
f3268125 » jwiegley 2008-05-14 Added support for iteration. 353 def __getitem__(self, idx_or_partial_hash):
354 issue = None
57f66a2b » Toby Moore 2008-05-15 Fixed the IssueSet.__getite... 355 try:
356 idx = int(idx_or_partial_hash) - 1
357 issue = self.shelf[self.shelf.keys()[idx]]
358 except:
d602dfb9 » jwiegley 2008-05-17 Corrected indentation. 359 clean = lambda x: x.replace('issue.xml', '').replace('/','')
360 matching = [(clean(key), key) for key in self.shelf.iterkeys()
a71d32ca » ktf 2008-06-22 Initial comment support. 361 if clean(key).startswith(idx_or_partial_hash) and not "comment" in clean(key)]
d602dfb9 » jwiegley 2008-05-17 Corrected indentation. 362 if len(matching) == 0:
363 pass
364 elif len(matching) == 1:
365 issue = self.shelf[matching[0][1]]
366 else:
367 print ("Ambiguous hash matches:\n" +
368 '\t\n'.join(a[0] for a in matching))
f3268125 » jwiegley 2008-05-14 Added support for iteration. 369
370 if not issue:
d602dfb9 » jwiegley 2008-05-17 Corrected indentation. 371 raise Exception("There is no issue matching the identifier '%s'.\n" %
372 idx_or_partial_hash)
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 373
f3268125 » jwiegley 2008-05-14 Added support for iteration. 374 return issue
375
376 def __delitem__(self, idx_or_partial_hash):
377 del self.shelf[None] # jww (2008-05-14): NYI
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 378 assert False
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 379
380 def issues_cache_file(self):
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 381 assert False
382
f3268125 » jwiegley 2008-05-14 Added support for iteration. 383 def load_state(self):
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 384 """Given a newly created IssueSet object as a template, see if we can
385 restore the cached version of the data from disk, and then check whether
386 it's still valid. This can _greatly_ speed up subsequent list and show
387 operations.
388
389 The reason why a newly created template exists is to abstract
390 DVCS-specific behavior, such as the location of the cache file.
391
392 Thus, a typical session looks like this:
dc1b7f66 » jwiegley 2008-05-12 Re-implemented the 'show' c... 393
f3268125 » jwiegley 2008-05-14 Added support for iteration. 394 issueSet = GitIssueSet()
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 395
396 if ... looking at issues list is required ...:
f3268125 » jwiegley 2008-05-14 Added support for iteration. 397 issueSet = issueSet.load_state()
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 398 ... use the issue data ..."""
f3268125 » jwiegley 2008-05-14 Added support for iteration. 399 cache_file = self.issues_cache_file()
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 400 if isfile(cache_file):
401 fd = open(cache_file, 'rb')
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 402 if options.verbose:
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 403 print "Cache: Loading saved issues data"
404 try:
405 cachedIssueSet = cPickle.load(fd)
406 finally:
407 fd.close()
408
f3268125 » jwiegley 2008-05-14 Added support for iteration. 409 if cachedIssueSet.cache_version == self.cache_version:
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 410 if options.verbose:
411 print "Cache: It is valid and usable"
412 return cachedIssueSet
dc1b7f66 » jwiegley 2008-05-12 Re-implemented the 'show' c... 413
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 414 if options.verbose:
415 print "Cache: No longer valid, throwing it away"
dc1b7f66 » jwiegley 2008-05-12 Re-implemented the 'show' c... 416
cab44d22 » jwiegley 2008-05-14 Added the initial infra-str... 417 # We can't use or rely on the cache, so read all details from disk and
418 # then mark the IssueSet dirty so that it gets saved back again when
419 # we exit.
f3268125 » jwiegley 2008-05-14 Added support for iteration. 420 try:
421 return object_from_string(self.shelf['project.xml'])
422 except:
423 return self
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 424
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 425 def save_state(self):
426 """Write an IssueSet to disk in object form, for fast loading on the next
427 iteration. This is only done if there are actual changes to write."""
f3268125 » jwiegley 2008-05-14 Added support for iteration. 428 if not self.dirty:
429 return
430
431 self.shelf.sync()
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 432
f3268125 » jwiegley 2008-05-14 Added support for iteration. 433 cache_file = self.issues_cache_file()
434 cache_file_dir = os.path.dirname(cache_file)
435
436 if not isdir(cache_file_dir):
437 os.makedirs(cache_file_dir)
438
439 fd = open(cache_file, 'wb')
440 try:
441 cPickle.dump(issueSet, fd)
442 finally:
443 fd.close()
444
445 self.dirty = False
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 446
5f44f6e0 » jwiegley 2008-05-12 Initial commit 447 ######################################################################
448
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 449 import xml.dom.minidom
450
451 def read_object(obj, file_descriptor):
452 return XmlReader.read(file_descriptor)
453
454 def object_from_string(str):
455 return XmlReader.readString(str)
456
457 class XmlReader:
458 def read(cls, fd):
459 doc = xml.dom.minidom.parse(fd)
460 data = XmlRipper.rip(doc.firstChild)
461 doc.unlink()
462 return data
463
464 read = classmethod(read)
465
466 def readString(cls, data):
467 doc = xml.dom.minidom.parseString(data)
468 data = XmlRipper.rip(doc.firstChild)
469 doc.unlink()
470 return data
471
472 readString = classmethod(readString)
473
474 class XmlStringRipper:
475 def rip(cls, node):
dd629ab5 » jwiegley 2008-05-14 Always write out all the XM... 476 return node.data[1:-1]
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 477
478 rip = classmethod(rip)
479
480 class XmlListRipper:
481 def rip(cls, node):
482 assert False
483
484 rip = classmethod(rip)
485
486 class XmlDateTimeRipper:
487 def rip(cls, node):
dd629ab5 » jwiegley 2008-05-14 Always write out all the XM... 488 return datetime.strptime(node.childNodes[0].data[1:-1], iso_fmt)
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 489
490 rip = classmethod(rip)
491
492 class XmlPersonRipper:
493 def rip(cls, node):
dd629ab5 » jwiegley 2008-05-14 Always write out all the XM... 494 person = Person(node.childNodes[1].childNodes[0].data[1:-1],
495 node.childNodes[3].childNodes[0].data[1:-1])
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 496 return person
497
498 rip = classmethod(rip)
499
500 class XmlIssueRipper:
501 def rip(cls, node):
dd629ab5 » jwiegley 2008-05-14 Always write out all the XM... 502 created = XmlRipper.rip(node.childNodes[1].childNodes[1])
503 author = XmlRipper.rip(node.childNodes[3].childNodes[1])
504 title = XmlRipper.rip(node.childNodes[5].firstChild)
505
506 issue = Issue(None, author, title)
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 507 issue.created = created
cab44d22 » jwiegley 2008-05-14 Added the initial infra-str... 508 issue.dirty = False
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 509
510 return issue
511
512 rip = classmethod(rip)
513
514 class XmlIssueSetRipper:
515 pass
516
517 class XmlRipper:
518 def rip(cls, node):
dd629ab5 » jwiegley 2008-05-14 Always write out all the XM... 519 if node.nodeType == xml.dom.minidom.Node.TEXT_NODE:
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 520 return XmlStringRipper.rip(node)
521 elif node.nodeName == 'datetime':
522 return XmlDateTimeRipper.rip(node)
523 elif node.nodeName == 'person':
524 return XmlPersonRipper.rip(node)
525 elif node.nodeName == 'list':
526 return XmlListRipper.rip(node)
527 elif node.nodeName == 'issue':
528 return XmlIssueRipper.rip(node)
529 elif node.nodeName == 'issue-set':
530 return XmlIssueSetRipper.rip(node)
531 else:
dd629ab5 » jwiegley 2008-05-14 Always write out all the XM... 532 print node.nodeType
533 print node.nodeName
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 534 assert False
535
536 rip = classmethod(rip)
537
538 ######################################################################
539
dd629ab5 » jwiegley 2008-05-14 Always write out all the XM... 540 def write_object(obj, file_descriptor = sys.stdout):
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 541 XmlWriter.write(XmlBuilder.build(obj), fd = file_descriptor)
542
543 def object_to_string(obj):
544 buffer = StringIO()
545 XmlWriter.write(XmlBuilder.build(obj), fd = buffer)
546 return buffer.getvalue()
547
548 class XmlWriter:
549 def write(cls, doc, no_header = False, fd = sys.stdout):
550 if no_header:
551 buffer = StringIO()
552 buffer.write(doc.toprettyxml(indent = "", encoding = "utf-8"))
553 fd.write(re.sub('^.+\n', '', buffer.getvalue()))
554 else:
555 fd.write(doc.toprettyxml(indent = "", encoding = "utf-8"))
556 doc.unlink()
557
558 write = classmethod(write)
559
560 class XmlStringBuilder:
561 def build(cls, data, node, doc):
562 node.appendChild(doc.createTextNode(data))
563
564 build = classmethod(build)
565
566 class XmlListBuilder:
567 def build(cls, data, node, doc):
568 element = doc.createElement("list")
569 for child in data:
570 XmlBuilder.build(doc, element, child)
571 node.appendChild(element)
572
573 build = classmethod(build)
574
575 class XmlDateTimeBuilder:
576 def build(cls, data, node, doc):
577 element = doc.createElement("datetime")
578 element.appendChild(doc.createTextNode(data.strftime(iso_fmt)))
579 node.appendChild(element)
580
581 build = classmethod(build)
582
583 class XmlPersonBuilder:
584 def build(cls, data, node, doc):
585 person = doc.createElement("person")
586
587 name = doc.createElement("name")
588 name.appendChild(doc.createTextNode(data.name))
589 person.appendChild(name)
590
591 email = doc.createElement("email")
592 email.appendChild(doc.createTextNode(data.email))
593 person.appendChild(email)
594
595 node.appendChild(person)
596
597 build = classmethod(build)
598
599 class XmlIssueBuilder:
4d9fb480 » jwiegley 2008-05-13 Move `doc` argument for two... 600 def build(cls, issue, node, doc):
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 601 issueNode = doc.createElement("issue")
602
603 created = doc.createElement("created")
604 XmlBuilder.build(issue.created, created, doc)
605 issueNode.appendChild(created)
606
607 author = doc.createElement("author")
608 XmlBuilder.build(issue.author, author, doc)
609 issueNode.appendChild(author)
610
611 title = doc.createElement("title")
612 XmlBuilder.build(issue.title, title, doc)
613 issueNode.appendChild(title)
614
dd629ab5 » jwiegley 2008-05-14 Always write out all the XM... 615 summary = doc.createElement("summary")
616 XmlBuilder.build(issue.summary, summary, doc)
617 issueNode.appendChild(summary)
618
619 description = doc.createElement("description")
620 XmlBuilder.build(issue.description, description, doc)
621 issueNode.appendChild(description)
622
623 reporters = doc.createElement("reporters")
624 XmlBuilder.build(issue.reporters, reporters, doc)
625 issueNode.appendChild(reporters)
626
627 owners = doc.createElement("owners")
628 XmlBuilder.build(issue.owners, owners, doc)
629 issueNode.appendChild(owners)
630
631 assigned = doc.createElement("assigned")
632 XmlBuilder.build(issue.assigned, assigned, doc)
633 issueNode.appendChild(assigned)
634
635 carbons = doc.createElement("carbons")
636 XmlBuilder.build(issue.carbons, carbons, doc)
637 issueNode.appendChild(carbons)
638
639 status = doc.createElement("status")
640 XmlBuilder.build(issue.status, status, doc)
641 issueNode.appendChild(status)
642
643 resolution = doc.createElement("resolution")
644 XmlBuilder.build(issue.resolution, resolution, doc)
645 issueNode.appendChild(resolution)
646
647 issue_type = doc.createElement("type")
648 XmlBuilder.build(issue.issue_type, issue_type, doc)
649 issueNode.appendChild(issue_type)
650
651 components = doc.createElement("components")
652 XmlBuilder.build(issue.components, components, doc)
653 issueNode.appendChild(components)
654
655 version = doc.createElement("version")
656 XmlBuilder.build(issue.version, version, doc)
657 issueNode.appendChild(version)
658
659 milestone = doc.createElement("milestone")
660 XmlBuilder.build(issue.milestone, milestone, doc)
661 issueNode.appendChild(milestone)
662
663 severity = doc.createElement("severity")
664 XmlBuilder.build(issue.severity, severity, doc)
665 issueNode.appendChild(severity)
666
667 priority = doc.createElement("priority")
668 XmlBuilder.build(issue.priority, priority, doc)
669 issueNode.appendChild(priority)
670
671 tags = doc.createElement("tags")
672 XmlBuilder.build(issue.tags, tags, doc)
673 issueNode.appendChild(tags)
674
675 modified = doc.createElement("modified")
676 XmlBuilder.build(issue.modified, modified, doc)
677 issueNode.appendChild(modified)
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 678
679 node.appendChild(issueNode)
680
681 build = classmethod(build)
682
a71d32ca » ktf 2008-06-22 Initial comment support. 683 class XmlCommentBuilder:
684 def build(cls, comment, node, doc):
685 commentNode = doc.createElement("comment")
686
687 created = doc.createElement("created")
688 XmlBuilder.build(comment.created, created, doc)
689 commentNode.appendChild(created)
690
691 author = doc.createElement("author")
692 XmlBuilder.build(comment.author, author, doc)
693 commentNode.appendChild(author)
694
695 commentText = doc.createElement("comment")
696 XmlBuilder.build(comment.comment, commentText, doc)
697 commentNode.appendChild(commentText)
698 node.appendChild(commentNode)
699
700 build = classmethod(build)
701
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 702 #class XmlIssueChangesBuilder:
703 # def build(cls, data, node, doc):
704 # changes = doc.createElement("changes")
705 # doc.appendChild(changes)
706 #
707 # for field_name in self.changes.keys():
708 # field = doc.createElement("field")
709 # field.setAttribute("name", field_name)
710 #
711 # data = self.changes[field_name]
712 #
713 # before = doc.createElement("before")
714 # XmlBuilder.build(data[0], before, doc)
715 # field.appendChild(before)
716 #
717 # after = doc.createElement("after")
718 # XmlBuilder.build(data[1], after, doc)
719 # field.appendChild(after)
720 #
721 # changes.appendChild(field)
722 #
723 # node.appendChild(changes)
724 #
725 # build = classmethod(build)
726
727 class XmlIssueSetBuilder:
4d9fb480 » jwiegley 2008-05-13 Move `doc` argument for two... 728 def build(cls, issueSet, node, doc):
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 729 set = doc.createElement("issue-set")
730
731 created = doc.createElement("created")
732 XmlBuilder.build(issueSet.created, created, doc)
733 set.appendChild(created)
734
dd629ab5 » jwiegley 2008-05-14 Always write out all the XM... 735 statuses = doc.createElement("statuses")
736 XmlBuilder.build(issueSet.statuses, statuses, doc)
737 set.appendChild(statuses)
738
739 resolutions = doc.createElement("resolutions")
740 XmlBuilder.build(issueSet.resolutions, resolutions, doc)
741 set.appendChild(resolutions)
742
743 issue_types = doc.createElement("types")
744 XmlBuilder.build(issueSet.issue_types, issue_types, doc)
745 set.appendChild(issue_types)
746
747 components = doc.createElement("components")
748 XmlBuilder.build(issueSet.components, components, doc)
749 set.appendChild(components)
750
751 versions = doc.createElement("versions")
752 XmlBuilder.build(issueSet.versions, versions, doc)
753 set.appendChild(versions)
754
755 milestones = doc.createElement("milestones")
756 XmlBuilder.build(issueSet.milestones, milestones, doc)
757 set.appendChild(milestones)
758
759 severities = doc.createElement("severities")
760 XmlBuilder.build(issueSet.severities, severities, doc)
761 set.appendChild(severities)
762
763 priorities = doc.createElement("priorities")
764 XmlBuilder.build(issueSet.priorities, priorities, doc)
765 set.appendChild(priorities)
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 766
cab44d22 » jwiegley 2008-05-14 Added the initial infra-str... 767 modified = doc.createElement("modified")
768 XmlBuilder.build(issueSet.modified, modified, doc)
769 set.appendChild(modified)
770
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 771 node.appendChild(set)
772
773 build = classmethod(build)
774
775 class XmlBuilder:
776 def build(cls, data, node = None, doc = None):
dd629ab5 » jwiegley 2008-05-14 Always write out all the XM... 777 if data is None:
778 pass
779 elif isinstance(data, datetime):
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 780 assert doc
781 XmlDateTimeBuilder.build(data, node, doc)
782 elif isinstance(data, Person):
783 assert doc
784 XmlPersonBuilder.build(data, node, doc)
785 elif isinstance(data, list):
786 assert doc
787 XmlListBuilder.build(data, node, doc)
788 elif isinstance(data, str):
789 assert doc
790 XmlStringBuilder.build(data, node, doc)
791 elif isinstance(data, Issue):
792 assert not doc
793 doc = xml.dom.minidom.Document()
794 XmlIssueBuilder.build(data, doc, doc)
795 elif isinstance(data, IssueSet):
796 assert not doc
797 doc = xml.dom.minidom.Document()
798 XmlIssueSetBuilder.build(data, doc, doc)
a71d32ca » ktf 2008-06-22 Initial comment support. 799 elif isinstance(data, Comment):
800 assert not doc
801 doc = xml.dom.minidom.Document()
802 XmlCommentBuilder.build(data, doc, doc)
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 803 else:
a71d32ca » ktf 2008-06-22 Initial comment support. 804 print "Unknown type %s" % data
dc95c9f3 » jwiegley 2008-05-13 Abstracted the use of XML i... 805 assert False
806
807 return doc
808
809 build = classmethod(build)
810
811 ######################################################################
812
3bb55535 » jwiegley 2008-05-14 git-issues is now using git... 813 class GitIssue(Issue):
814 def get_name(self):
815 if not self.name:
816 hash_func = self.issueSet.shelf.hash_blob
817 name = hash_func(str(self.created) + str(self.author) +
818 self.title)
819 self.name = name
820 return self.name
821
1efa9acd » ktf 2008-06-20 Support for managing comments. 822 class GitComment(Comment):
823 def get_name(self):
824 if not self.name:
825 hash_func = self.issue.issueSet.shelf.hash_blob
a71d32ca » ktf 2008-06-22 Initial comment support. 826 name = hash_func(str(self.created)
1efa9acd » ktf 2008-06-20 Support for managing comments. 827 + str(self.author)
828 + self.comment)
829 self.name = name
830 return self.name
831
3bb55535 » jwiegley 2008-05-14 git-issues is now using git... 832 class xml_gitbook(gitshelve.gitbook):
31c9ebdc » jwiegley 2008-05-14 Changed the implementation ... 833 def serialize_data(self, data):
834 return object_to_string(data)
b6d2d87d » jwiegley 2008-05-14 Created a gitshelve Python ... 835
f3268125 » jwiegley 2008-05-14 Added support for iteration. 836 def deserialize_data(self, data):
31c9ebdc » jwiegley 2008-05-14 Changed the implementation ... 837 return object_from_string(data)
b6d2d87d » jwiegley 2008-05-14 Created a gitshelve Python ... 838
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 839 class GitIssueSet(IssueSet):
840 """This object implements all the command necessary to interact with Git
841 for the purpose of storing and distributing issues."""
5f44f6e0 » jwiegley 2008-05-12 Initial commit 842 def __init__(self):
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 843 self.GIT_DIR = None
844 self.GIT_AUTHOR = None
3bb55535 » jwiegley 2008-05-14 git-issues is now using git... 845 IssueSet.__init__(self, gitshelve.open('issues',
846 book_type = xml_gitbook))
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 847
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 848 def git_directory(self):
849 if self.GIT_DIR is None:
19081aaf » jwiegley 2008-05-14 Changed a few calls to git(... 850 self.GIT_DIR = gitshelve.git('rev-parse', '--git-dir')
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 851 return self.GIT_DIR
852
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 853 def issues_cache_file(self):
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 854 return join(self.git_directory(), "issues")
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 855
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 856 def current_author(self):
857 if self.GIT_AUTHOR is None:
19081aaf » jwiegley 2008-05-14 Changed a few calls to git(... 858 self.GIT_AUTHOR = Person(gitshelve.git('config', 'user.name'),
859 gitshelve.git('config', 'user.email'))
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 860 return self.GIT_AUTHOR
861
f3268125 » jwiegley 2008-05-14 Added support for iteration. 862 def allocate_issue(self, title):
863 return GitIssue(self, self.current_author(), title)
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 864
1efa9acd » ktf 2008-06-20 Support for managing comments. 865 def allocate_comment(self, issue, commentText):
866 return GitComment(issue, self.current_author(), commentText)
867
f3268125 » jwiegley 2008-05-14 Added support for iteration. 868 ######################################################################
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 869
f3268125 » jwiegley 2008-05-14 Added support for iteration. 870 def format_long_text(text, indent = 13):
871 if not text:
872 return "<none>"
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 873
d602dfb9 » jwiegley 2008-05-17 Corrected indentation. 874 lines = text.split('\n')
dd629ab5 » jwiegley 2008-05-14 Always write out all the XM... 875
f3268125 » jwiegley 2008-05-14 Added support for iteration. 876 buffer = StringIO()
877 first = True
878 for line in lines:
879 if not first:
880 buffer.write("\n%s" % (" " * indent))
881 else:
882 first = False
883 buffer.write(line)
dd629ab5 » jwiegley 2008-05-14 Always write out all the XM... 884
f3268125 » jwiegley 2008-05-14 Added support for iteration. 885 return buffer.getvalue()
5f44f6e0 » jwiegley 2008-05-12 Initial commit 886
f3268125 » jwiegley 2008-05-14 Added support for iteration. 887 def format_people_list(people, indent = 13):
888 if not people:
889 return "<no one yet>"
5f44f6e0 » jwiegley 2008-05-12 Initial commit 890
f3268125 » jwiegley 2008-05-14 Added support for iteration. 891 buffer = StringIO()
892 first = True
893 for person in people:
894 if not first:
895 buffer.write(",\n%s" % (" " * indent))
896 else:
897 first = False
898 buffer.write(person)
5f44f6e0 » jwiegley 2008-05-12 Initial commit 899
f3268125 » jwiegley 2008-05-14 Added support for iteration. 900 return buffer.getvalue()
5f44f6e0 » jwiegley 2008-05-12 Initial commit 901
5be27b86 » sbohrer 2008-07-01 Perform more extensive chec... 902 def terminal_width():
903 """Return terminal width."""
904 width = 0
905 try:
906 import struct, fcntl, termios
907 s = struct.pack('HHHH', 0, 0, 0, 0)
908 x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
909 width = struct.unpack('HHHH', x)[1]
910 except:
911 pass
912 if width <= 0:
913 if os.environ.has_key("COLUMNS"):
914 width = int(os.getenv("COLUMNS"))
915 if width <= 0:
916 width = 80
917 return width
918
5f44f6e0 » jwiegley 2008-05-12 Initial commit 919 ######################################################################
920
e0788547 » sbohrer 2008-07-01 Document missing commands a... 921 parser = optparse.OptionParser(usage="""Usage: git-issues [options] <command> [command-options]
9814d6b7 » Giulio Eulisse 2008-05-22 Better help message. 922
923 Commands:
e0788547 » sbohrer 2008-07-01 Document missing commands a... 924 init Creates a copy of git-issues repository in .gitissues in the
925 current git repository.
9814d6b7 » Giulio Eulisse 2008-05-22 Better help message. 926 list Lists tickets for this repository
927 new Creates a new ticket for this repository
928 show/dump Shows the given ticket
e0788547 » sbohrer 2008-07-01 Document missing commands a... 929 change Change options for the given ticket
930 edit edit options for the given ticket in text editor
931 comment Add a comment to the given ticket
932 close Close the given ticket""")
5f44f6e0 » jwiegley 2008-05-12 Initial commit 933 parser.add_option("-v", "--verbose",
934 action = "store_true",
935 dest = "verbose",
936 default = False,
937 help = "report activity options.verbosely")
938
919b0a9e » ktf 2008-05-22 Option --print-new-bugs added. 939 parser.add_option("--print-new-bugs",
940 action = "store_true",
941 dest = "printNewBugs",
942 default = False,
943 help = "prints out a formatted string with the bug summary and id. Usuful for in editor usage.")
944
401019fa » ktf 2008-05-27 Option --filter-status adde... 945 parser.add_option("--filter-status",
946 dest="filterStatus",
947 default="closed",
57281f8b » ktf 2008-09-12 Better help message for --f... 948 help = """do not print a issue if it is in one of
401019fa » ktf 2008-05-27 Option --filter-status adde... 949 the stati specified (column separated) by this option.""".replace("\n",""))
950
d352c9e8 » ktf 2008-09-12 Adds a --filter-tags option... 951 parser.add_option("--filter-tags",
952 dest="filterTags",
953 default="",
954 help = """Prints only the issues with one of the following
955 tags (column separated) associated to it.""")
956
4aedf47c » ktf 2008-05-27 Better (resizable) printout... 957 parser.add_option("--screen-width",
958 dest="screenWidth",
5be27b86 » sbohrer 2008-07-01 Perform more extensive chec... 959 default=terminal_width(),
4aedf47c » ktf 2008-05-27 Better (resizable) printout... 960 help = "Width of the terminal we are printing to.")
961
81b89b3d » ktf 2008-06-01 Adding option `--status` to... 962 parser.add_option("--status",
963 dest="status",
964 default=None,
965 metavar="STATUS",
966 help="Set the status of the issue to STATUS when creating it.")
967
5f44f6e0 » jwiegley 2008-05-12 Initial commit 968 (options, args) = parser.parse_args()
969
f3268125 » jwiegley 2008-05-14 Added support for iteration. 970 gitshelve.verbose = options.verbose
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 971
4aedf47c » ktf 2008-05-27 Better (resizable) printout... 972
5f44f6e0 » jwiegley 2008-05-12 Initial commit 973 ######################################################################
974
4df5a51d » ktf 2008-06-20 New helper method "inputFro... 975 def inputFromEditor(originalText):
d10f3556 » sbohrer 2008-06-30 Fix indentation 976 fd, tempFile = mkstemp()
977 f = open(tempFile,"w")
978 f.write(originalText or "")
979 f.close()
980
981 defaultEditor = "vi"
982 if platform.system() == "Windows":
983 defaultEditor = "notepad"
984 if os.environ.has_key("VISUAL"):
985 defaultEditor = os.getenv("VISUAL")
986 elif os.environ.has_key("EDITOR"):
987 defualtEditor = os.getenv("EDITOR")
988 editCommand = "%s %s" % (defaultEditor, tempFile)
989 if os.system(editCommand) != 0:
990 os.unlink(tempFile)
991 print "Error while executing %s" % editCommand
992 sys.exit(1)
993 contents = open(tempFile).read()
994 os.unlink(tempFile)
995 return contents
4aedf47c » ktf 2008-05-27 Better (resizable) printout... 996
b6d2d87d » jwiegley 2008-05-14 Created a gitshelve Python ... 997 if __name__ == '__main__':
5f44f6e0 » jwiegley 2008-05-12 Initial commit 998
b6d2d87d » jwiegley 2008-05-14 Created a gitshelve Python ... 999 if len(args) == 0:
9814d6b7 » Giulio Eulisse 2008-05-22 Better help message. 1000 parser.print_help()
b6d2d87d » jwiegley 2008-05-14 Created a gitshelve Python ... 1001 sys.exit(1)
1002
1003 command = args[0]
1004 args = args[1:]
5f44f6e0 » jwiegley 2008-05-12 Initial commit 1005
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 1006 ######################################################################
1007
7eba73b6 » jwiegley 2008-07-31 Only 'show' as many task de... 1008 # jww (2008-05-12): Pick the appropriate IssueSet to use based on the
1009 # environment.
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 1010
f3268125 » jwiegley 2008-05-14 Added support for iteration. 1011 issueSet = GitIssueSet().load_state()
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 1012
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 1013 ######################################################################
bda8dbc9 » jwiegley 2008-05-12 Moving toward an abstracted... 1014
7173d82a » Giulio Eulisse 2008-05-21 git-issues `init` command a... 1015 if command == "init":
1016 from os.path import split, join, exists, dirname
1017 from os import getcwd, makedirs
1018 from shutil import copy
1019 path = getcwd()
1020 while not exists(join(path,".git")):
1021 path,extra = split(path)
1022 if not extra:
1023 print "Unable to find a git repository. "
1024 print "Make sure you ran `git init` at some point."
1025 sys.exit(1)
1026 issuesdir = join(path,".gitissues")
1027 if exists(issuesdir):
1028 print "git-issues helper directory %s already exists." % issuesdir
1029 print "Doing nothing."
1030 sys.exit(1)
1031 makedirs(issuesdir)
1032 copy(__file__, issuesdir)
1033 copy(join(dirname(__file__), "gitshelve.py"), issuesdir)
1034 copy(join(dirname(__file__), "t_gitshelve.py"), issuesdir)
1035 copy(join(dirname(__file__), "README"), issuesdir)
1036 copy(join(dirname(__file__), "LICENSE"), issuesdir)
f3b7babf » edrik 2008-08-20 exit(0) on ok init 1037 sys.exit(0)
1038
1039 ######################################################################
1040
1041 elif command == "list":
4aedf47c » ktf 2008-05-27 Better (resizable) printout... 1042 header = " # Id Title%sState Date Assign Tags"
1043 width = int(options.screenWidth)
1044 titleWidth = width - len(header) + 2
1045 print header % "".join([" " for x in xrange(titleWidth)])
1046 print "".join (["-" for x in xrange(width)])
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 1047
b6d2d87d » jwiegley 2008-05-14 Created a gitshelve Python ... 1048 index = 1
401019fa » ktf 2008-05-27 Option --filter-status adde... 1049 filteredStati = options.filterStatus.split(":")
d352c9e8 » ktf 2008-09-12 Adds a --filter-tags option... 1050 wantedTags = dict([(tag,1) for tag in options.filterTags.split(":") if tag])
f3b7babf » edrik 2008-08-20 exit(0) on ok init 1051
f3268125 » jwiegley 2008-05-14 Added support for iteration. 1052 for item in issueSet.shelf.iteritems():
a71d32ca » ktf 2008-06-22 Initial comment support. 1053 if "comment" in item[0]:
1054 continue
3bb55535 » jwiegley 2008-05-14 git-issues is now using git... 1055 issue = item[1].get_data()
401019fa » ktf 2008-05-27 Option --filter-status adde... 1056 if issue.status in filteredStati:
1057 continue
d352c9e8 » ktf 2008-09-12 Adds a --filter-tags option... 1058 if wantedTags and not issue.tags:
1059 continue
1060 if wantedTags:
1061 matchingTags = [tag for tag in issue.tags.split(", ") if wantedTags.has_key(tag)]
1062 if not matchingTags:
1063 continue
4aedf47c » ktf 2008-05-27 Better (resizable) printout... 1064 formatString = "%4d %s %-" + str(titleWidth+len("Title")-1) + "s %-6s %5s %6s %s"
1065 print formatString % \
b6d2d87d » jwiegley 2008-05-14 Created a gitshelve Python ... 1066 (index, issue.name[:7], issue.title, issue.status,
1067 issue.created and issue.created.strftime('%m/%d'),
1068 str(issue.author)[:6], '')
1069 index += 1
5f44f6e0 » jwiegley 2008-05-12 Initial commit 1070
b6d2d87d » jwiegley 2008-05-14 Created a gitshelve Python ... 1071 print
f3b7babf » edrik 2008-08-20 exit(0) on ok init 1072
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 1073 ######################################################################
1074
b6d2d87d » jwiegley 2008-05-14 Created a gitshelve Python ... 1075 elif command == "show" or command == "dump":
1076 if len(args) == 0:
e0788547 » sbohrer 2008-07-01 Document missing commands a... 1077 print "Usage: %s %s <issue-id | index>" % (sys.argv[0], command)
b6d2d87d » jwiegley 2008-05-14 Created a gitshelve Python ... 1078 else:
73bc61c1 » sbohrer 2008-07-02 git-issues show: Only print... 1079 issue = issueSet[args[0]]
1080 comments = "\n ".join(["Comment (%s): %s" % (comment[0:7],
1081 issueSet.get_comment(comment[0:7]).comment)
1082 for comment in issue.comments])
1083 if command == "show":
7eba73b6 » jwiegley 2008-07-31 Only 'show' as many task de... 1084 if issue.title:
1085 print " Title:", issue.title
1086 if issue.summary:
1087 print " Summary:", format_long_text(issue.summary)
1088 print
1089 if issue.description:
1090 print " Description:", format_long_text(issue.description)
1091 print
1092 if issue.author:
1093 print " Author:", issue.author
1094 if issue.reporters:
1095 print " Reporter(s):", format_people_list(issue.reporters)
1096 if issue.owners:
1097 print " Owner(s):", format_people_list(issue.owners)
1098 if issue.assigned:
1099 print " Assigned:", format_people_list(issue.assigned)
1100 if issue.carbons:
1101 print " Cc:", format_people_list(issue.carbons)
1102
1103 if issue.issue_type:
1104 print " Type:", issue.issue_type
1105 if issue.status:
1106 print " Status:", issue.status
1107 if issue.resolution:
1108 print " Resolution:", issue.resolution
1109 if issue.components:
1110 print " Components:", issue.components
1111 if issue.version:
1112 print " Version:", issue.version
1113 if issue.milestone:
1114 print " Milestone:", issue.milestone
1115 if issue.severity:
1116 print " Severity:", issue.severity
1117 if issue.priority:
1118 print " Priority:", issue.priority
1119 if issue.tags:
1120 print " Tags:", issue.tags
1121
1122 print " Created:", issue.created
1123 if issue.modified:
1124 print " Modified:", issue.modified
73bc61c1 » sbohrer 2008-07-02 git-issues show: Only print... 1125 else:
1126 write_object(issue)
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 1127
1128 ######################################################################
1129
b6d2d87d » jwiegley 2008-05-14 Created a gitshelve Python ... 1130 elif command == "change":
1131 if len(args) == 0:
e0788547 » sbohrer 2008-07-01 Document missing commands a... 1132 print "Usage: %s change <issue-id> <field> <value>" % sys.argv[0]
addd4b0f » ktf 2008-05-22 All tabs converted to spaces. 1133 else:
1134 issue = issueSet[args[0]]
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 1135
addd4b0f » ktf 2008-05-22 All tabs converted to spaces. 1136 # jww (2008-05-13): Need to parse datetime, lists, and people
f5850934 » ktf 2008-06-01 Does not use eval anymore t... 1137 method = getattr(issue, "set_" + args[1])
ccd521d9 » ktf 2008-08-07 Checks if `method()` is cal... 1138 try:
1139 method(args[2])
1140 except IndexError,e:
1141 print "Index error."
1142 print args
1143 sys.exit(1)
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 1144 ######################################################################
f3b7babf » edrik 2008-08-20 exit(0) on ok init 1145
d0bafd0b » ktf 2008-06-03 `git-issues edit` command a... 1146 elif command == "edit":
1147 if len(args) == 0:
e0788547 » sbohrer 2008-07-01 Document missing commands a... 1148 print "Usage: %s edit <issue-id> <field>" % sys.argv[0]
d0bafd0b » ktf 2008-06-03 `git-issues edit` command a... 1149 sys.exit(1)
1150 else:
1151 issue = issueSet[args[0]]
bbf90ce3 » ktf 2008-06-10 Now handles the case git-is... 1152 if len(args) != 2:
e0788547 » sbohrer 2008-07-01 Document missing commands a... 1153 print "Usage: %s edit <issue-id> <field>" % sys.argv[0]
4df5a51d » ktf 2008-06-20 New helper method "inputFro... 1154 sys.exit(1)
bf0368e2 » ktf 2008-07-20 git-issues edit now reads i... 1155 if not sys.stdin.isatty():
1156 contents = sys.stdin.read()
1157 else:
4c5e4bd9 » davglass 2009-02-20 Fixed case issue with edit ... 1158 cmd = args[1].lower()
1159 contents = inputFromEditor(getattr(issue, cmd))
1160 getattr(issue, "set_"+cmd)(contents)
d0bafd0b » ktf 2008-06-03 `git-issues edit` command a... 1161
1162 ######################################################################
5f44f6e0 » jwiegley 2008-05-12 Initial commit 1163
47f1c6f9 » ktf 2008-06-02 Helper command "close" adde... 1164 elif command == "close":
1165 if len(args) == 0:
e0788547 » sbohrer 2008-07-01 Document missing commands a... 1166 print "Usage: %s close <issue-id>" % basename(sys.argv[0])
47f1c6f9 » ktf 2008-06-02 Helper command "close" adde... 1167 sys.exit(1)
1168 issue = issueSet[args[0]]
1169 issue.set_status("closed")
1170
1171 ######################################################################
1172
b6d2d87d » jwiegley 2008-05-14 Created a gitshelve Python ... 1173 elif command == "new":
1174 if len(args) == 0:
e0788547 » sbohrer 2008-07-01 Document missing commands a... 1175 print "Usage: %s new <title>" % sys.argv[0]
addd4b0f » ktf 2008-05-22 All tabs converted to spaces. 1176 else:
57f66a2b » Toby Moore 2008-05-15 Fixed the IssueSet.__getite... 1177 issue = issueSet.new_issue(args[0])
81b89b3d » ktf 2008-06-01 Adding option `--status` to... 1178 if options.status:
1179 issue.set_status(options.status)
1180 else:
1181 issue.set_status("TODO")
f3b7babf » edrik 2008-08-20 exit(0) on ok init 1182
919b0a9e » ktf 2008-05-22 Option --print-new-bugs added. 1183 if options.printNewBugs:
81b89b3d » ktf 2008-06-01 Adding option `--status` to... 1184 print "%s: %s (%s)" % (issue.status, issue.title, issue.name[0:7])
a71d32ca » ktf 2008-06-22 Initial comment support. 1185 elif command == "comment":
1186 if len(args) == 0:
1187 print "Usage: %s comment <issue-id> <comment-title>" % sys.argv[0]
1188 sys.exit(1)
1189 issue = issueSet[args[0]]
1190 comment = issueSet.new_comment(issue, args[1])
1191 if options.printNewBugs:
1192 print "### Comment(%s): %s" % (comment.name[0:7], comment.comment)
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 1193
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 1194 ######################################################################
f3b7babf » edrik 2008-08-20 exit(0) on ok init 1195
8d73af63 » ktf 2008-06-20 Complain if the command giv... 1196 else:
1197 print "Unknown command %s" % command
b6d2d87d » jwiegley 2008-05-14 Created a gitshelve Python ... 1198 # If any of the commands made the issueSet dirty, (possibly) update the
1199 # repository and write out a new cache
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 1200
b6d2d87d » jwiegley 2008-05-14 Created a gitshelve Python ... 1201 issueSet.save_state()
9561b193 » jwiegley 2008-05-12 Data is all in XML in the i... 1202
1203 ######################################################################
badd2f0f » jwiegley 2008-05-12 Added code for dumping an i... 1204
b6d2d87d » jwiegley 2008-05-14 Created a gitshelve Python ... 1205 sys.exit(0)
5f44f6e0 » jwiegley 2008-05-12 Initial commit 1206
1207 # git-issue ends here