ditz-str 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. data/.document +5 -0
  2. data/Gemfile +14 -0
  3. data/Gemfile.lock +24 -0
  4. data/LICENSE +674 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +19 -0
  7. data/README.txt +143 -0
  8. data/Rakefile +55 -0
  9. data/VERSION +1 -0
  10. data/bin/ditz-str +189 -0
  11. data/bugs/issue-02615b8c3dd0382c92f350ce2158ecfe94d11ef8.yaml +22 -0
  12. data/bugs/issue-06a3bbf35a60c4da2d8ea0fdc86164263126d6b2.yaml +22 -0
  13. data/bugs/issue-0c00c1d7fdffaad304e62d79d9b3d5e92547055b.yaml +38 -0
  14. data/bugs/issue-20dad4b4533d6d76d496fe5970098f1eb8efd561.yaml +26 -0
  15. data/bugs/issue-360ae6529dbc66358fde6b532cbea79ece37a670.yaml +22 -0
  16. data/bugs/issue-5177d61bf3c2783f71ef63e6e2c5e720247ef699.yaml +18 -0
  17. data/bugs/issue-695b564c210da1965a2bb38eef782178aead6952.yaml +26 -0
  18. data/bugs/issue-7d0ce6429a9fb5fa09ce3376a8921a5ecb7ecfe5.yaml +34 -0
  19. data/bugs/issue-a04462fa22ab6e1b02cfdd052d1f6c6f491f08f5.yaml +22 -0
  20. data/bugs/issue-bca54ca5107eabc3b281701041cc36ea0641cbdd.yaml +26 -0
  21. data/bugs/issue-d0c7d04b014d705c5fd865e4d487b5e5b6983c33.yaml +26 -0
  22. data/bugs/issue-f94b879842aa0274aa74fc2833252d4a06ec65cc.yaml +22 -0
  23. data/bugs/project.yaml +18 -0
  24. data/data/ditz-str/blue-check.png +0 -0
  25. data/data/ditz-str/close.rhtml +39 -0
  26. data/data/ditz-str/component.rhtml +38 -0
  27. data/data/ditz-str/dropdown.css +11 -0
  28. data/data/ditz-str/dropdown.js +58 -0
  29. data/data/ditz-str/edit_issue.rhtml +53 -0
  30. data/data/ditz-str/green-bar.png +0 -0
  31. data/data/ditz-str/green-check.png +0 -0
  32. data/data/ditz-str/header.gif +0 -0
  33. data/data/ditz-str/header_over.gif +0 -0
  34. data/data/ditz-str/index.rhtml +148 -0
  35. data/data/ditz-str/issue.rhtml +152 -0
  36. data/data/ditz-str/issue_table.rhtml +28 -0
  37. data/data/ditz-str/new_component.rhtml +28 -0
  38. data/data/ditz-str/new_issue.rhtml +57 -0
  39. data/data/ditz-str/new_release.rhtml +29 -0
  40. data/data/ditz-str/plugins/git-sync.rb +83 -0
  41. data/data/ditz-str/plugins/git.rb +153 -0
  42. data/data/ditz-str/plugins/issue-claiming.rb +174 -0
  43. data/data/ditz-str/red-check.png +0 -0
  44. data/data/ditz-str/release.rhtml +111 -0
  45. data/data/ditz-str/style.css +236 -0
  46. data/data/ditz-str/unassigned.rhtml +37 -0
  47. data/data/ditz-str/yellow-bar.png +0 -0
  48. data/ditz-str.gemspec +121 -0
  49. data/lib/ditzstr/brick.rb +251 -0
  50. data/lib/ditzstr/file-storage.rb +54 -0
  51. data/lib/ditzstr/hook.rb +67 -0
  52. data/lib/ditzstr/html.rb +104 -0
  53. data/lib/ditzstr/lowline.rb +201 -0
  54. data/lib/ditzstr/model-objects.rb +346 -0
  55. data/lib/ditzstr/model.rb +265 -0
  56. data/lib/ditzstr/operator.rb +593 -0
  57. data/lib/ditzstr/trollop.rb +614 -0
  58. data/lib/ditzstr/util.rb +61 -0
  59. data/lib/ditzstr/view.rb +16 -0
  60. data/lib/ditzstr/views.rb +157 -0
  61. data/lib/ditzstr.rb +69 -0
  62. data/man/ditz.1 +38 -0
  63. data/test/helper.rb +18 -0
  64. data/test/test_ditz-str.rb +7 -0
  65. metadata +219 -0
@@ -0,0 +1,57 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4
+
5
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
6
+ <head>
7
+ <title>New Issue</title>
8
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
9
+ <link rel="stylesheet" href="style.css" type="text/css" />
10
+ </head>
11
+
12
+ <body>
13
+
14
+ <div class="main">
15
+
16
+ <h1>New Issue</h1>
17
+ <div class="backptr"><%= link_to "index", "&laquo; #{@project.name} project page" %></div>
18
+ <%= "<form name=\"new_issue_form\" method=\"post\">" %>
19
+ Title<br/><input type="text" name="title" size="80"/><br/>
20
+ Description<br/><textarea cols="80" rows="10" name="description"/></textarea><br/>
21
+ Type<br/><select name="type">
22
+ <option>bugfix</option>
23
+ <option>feature</option>
24
+ <option>task</option>
25
+ </select><br/>
26
+
27
+ Component<br/>
28
+ <select name="component">
29
+ <% @project.components.each do |c| %>
30
+ <% if c.name==options[:component] %>
31
+ <%= "<option selected=\"true\">#{c.name}</option>" %>
32
+ <% else %>
33
+ <%= "<option>#{c.name}</option>" %>
34
+ <% end %>
35
+ <% end %></select><br/>
36
+
37
+ Release<br/>
38
+ <select name="release">
39
+ <% upcoming_rels.each do |r| %>
40
+ <% if r.name==options[:release] %>
41
+ <%= "<option selected=\"true\">#{r.name}</option>" %>
42
+ <% else %>
43
+ <%= "<option>#{r.name}</option>" %>
44
+ <% end %>
45
+ <% end %></select><br/>
46
+
47
+ Creator<br/>
48
+ <%= "<input type=\"text\" size=\"80\" value=\"#{options[:creator]}\"/><br/>" %>
49
+ <textarea cols="80" rows="10" name="comments"/></textarea><br/><br/>
50
+ <input type="submit" value="Add Issue"/>
51
+ </form>
52
+
53
+ </div>
54
+ <div class="footer">Generated by <a href="http://ditz.rubyforge.org/">ditz</a>.</div>
55
+
56
+ </body>
57
+ </html>
@@ -0,0 +1,29 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4
+
5
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
6
+ <head>
7
+ <%= "<title>#{@project.name} - New Release</title>" %>
8
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
9
+ <link rel="stylesheet" href="style.css" type="text/css" />
10
+ </head>
11
+
12
+ <body>
13
+
14
+ <div class="main">
15
+
16
+ <h1>New Release</h1>
17
+ <div class="backptr"><%= link_to "index", "&laquo; #{@project.name} project page" %></div>
18
+
19
+ <form name="new_issue_form" method="post">
20
+ Name<br/><input type="text" name="name" size="80"/><br/>
21
+ Comments<br/><textarea cols="80" rows="10" name="comments"/></textarea><br/>
22
+ <input type="submit" value="Add Release"/>
23
+ </form>
24
+
25
+ </div>
26
+ <div class="footer">Generated by <a href="http://ditz.rubyforge.org/">ditz</a>.</div>
27
+
28
+ </body>
29
+ </html>
@@ -0,0 +1,83 @@
1
+ ## git-sync ditz plugin
2
+ ##
3
+ ## This plugin is useful for when you want synchronized, non-distributed issue
4
+ ## coordination with other developers, and you're using git. It allows you to
5
+ ## synchronize issue updates with other developers by using the 'ditz sync'
6
+ ## command, which does all the git work of sending and receiving issue change
7
+ ## for you. However, you have to set things up in a very specific way for this
8
+ ## to work:
9
+ ##
10
+ ## 1. Your ditz state must be on a separate branch. I recommend calling it
11
+ ## 'bugs'. Create this branch, do a ditz init, and push it to the remote
12
+ ## repo. (This means you won't be able to mingle issue change and code
13
+ ## change in the same commits. If you care.)
14
+ ## 2. Make a checkout of the bugs branch in a separate directory, but NOT in
15
+ ## your code checkout. If you're developing in a directory called "project",
16
+ ## I recommend making a ../project-bugs/ directory, cloning the repo there
17
+ ## as well, and keeping that directory checked out to the 'bugs' branch.
18
+ ## (There are various complicated things you can do to make that directory
19
+ ## share git objects with your code directory, but I wouldn't bother unless
20
+ ## you really care about disk space. Just make it an independent clone.)
21
+ ## 3. Set that directory as your issue-dir in your .ditz-config file in your
22
+ ## code checkout directory. (This file should be in .gitignore, btw.)
23
+ ## 4. Run 'ditz reconfigure' and fill in the local branch name, remote
24
+ ## branch name, and remote repo for the issue tracking branch.
25
+ ##
26
+ ## Once that's set up, 'ditz sync' will change to the bugs checkout dir, bundle
27
+ ## up any changes you've made to issue status, push them to the remote repo,
28
+ ## and pull any new changes in too. All ditz commands will read from your bugs
29
+ ## directory, so you should be able to use ditz without caring about where
30
+ ## things are anymore.
31
+ ##
32
+ ## This complicated setup is necessary to avoid accidentally mingling code
33
+ ## change and issue change. With this setup, issue change is synchronized,
34
+ ## but how you synchronize code is still up to you.
35
+ ##
36
+ ## Usage:
37
+ ## 0. read all the above text very carefully
38
+ ## 1. add a line "- git-sync" to the .ditz-plugins file in the project
39
+ ## root
40
+ ## 2. run 'ditz reconfigure' and answer its questions
41
+ ## 3. run 'ditz sync' with abandon
42
+
43
+ module DitzStr
44
+
45
+ class Config
46
+ field :git_sync_local_branch, :prompt => "Local bugs branch name for ditz sync", :default => "bugs"
47
+ field :git_sync_remote_branch, :prompt => "Remote bugs branch name for ditz sync", :default => "bugs"
48
+ field :git_sync_remote_repo, :prompt => "Remote bugs repo name for ditz sync", :default => "origin"
49
+ end
50
+
51
+ class Operator
52
+ operation :sync, "Sync the repo containing ditz via git" do
53
+ opt :dry_run, "Dry run: print the commants, but don't execute them", :short => "n"
54
+ end
55
+ def sync project, config, opts
56
+ unless config.git_sync_local_branch
57
+ $stderr.puts "Please run ditz reconfigure and set the local and remote branch names"
58
+ return
59
+ end
60
+
61
+ Dir.chdir $project_root
62
+ puts "[in #{$project_root}]"
63
+ sh "git add *.yaml", :force => true, :fake => opts[:dry_run]
64
+ sh "git commit -m 'issue updates'", :force => true, :fake => opts[:dry_run]
65
+ sh "git pull", :fake => opts[:dry_run]
66
+ sh "git push #{config.git_sync_remote_repo} #{config.git_sync_local_branch}:#{config.git_sync_remote_branch}", :fake => opts[:dry_run]
67
+ puts
68
+ puts "Ditz issue state synchronized."
69
+ end
70
+
71
+ private
72
+
73
+ def sh s, opts={}
74
+ puts "+ #{s}"
75
+ return if opts[:fake]
76
+ unless system(s) || opts[:force]
77
+ $stderr.puts "non-zero (#{$?.exitstatus}) exit code: #{s}"
78
+ exit(-1)
79
+ end
80
+ end
81
+ end
82
+
83
+ end
@@ -0,0 +1,153 @@
1
+ ## git ditz plugin
2
+ ##
3
+ ## This plugin allows issues to be associated with git commits and git
4
+ ## branches. Git commits can be easily tagged with a ditz issue with the 'ditz
5
+ ## commit' command, and both 'ditz show' and the ditz HTML output will then
6
+ ## contain a list of associated commits for each issue.
7
+ ##
8
+ ## Issues can also be assigned a single git feature branch. In this case, all
9
+ ## commits on that branch will listed as commits for that issue. This
10
+ ## particular feature is fairly rudimentary, however---it assumes the reference
11
+ ## point is the 'master' branch, and once the feature branch is merged back
12
+ ## into master, the list of commits disappears.
13
+ ##
14
+ ## Two configuration variables are added, which, when specified, are used to
15
+ ## construct HTML links for the git commit id and branch names in the generated
16
+ ## HTML output.
17
+ ##
18
+ ## Commands added:
19
+ ## ditz set-branch: set the git branch of an issue
20
+ ## ditz commit: run git-commit, and insert the issue id into the commit
21
+ ## message.
22
+ ##
23
+ ## Usage:
24
+ ## 1. add a line "- git" to the .ditz-plugins file in the project root
25
+ ## 2. run ditz reconfigure, and enter the URL prefixes, if any, from
26
+ ## which to create commit and branch links.
27
+ ## 3. use 'ditz commit' with abandon.
28
+
29
+ require 'time'
30
+
31
+ module DitzStr
32
+ class Issue
33
+ field :git_branch, :ask => false
34
+
35
+ def git_commits
36
+ return @git_commits if @git_commits
37
+
38
+ filters = ["--grep=\"DitzStr-issue: #{id}\""]
39
+ filters << "master..#{git_branch}" if git_branch
40
+
41
+ output = filters.map do |f|
42
+ `git log --pretty=format:\"%aD\t%an <%ae>\t%h\t%s\" #{f}`
43
+ end.join
44
+
45
+ @git_commits = output.split(/\n/).map { |l| l.split("\t") }.
46
+ map { |date, email, hash, msg| [Time.parse(date).utc, email, hash, msg] }
47
+ end
48
+ end
49
+
50
+ class Config
51
+ field :git_commit_url_prefix, :prompt => "URL prefix to link git commits to", :default => ""
52
+ field :git_branch_url_prefix, :prompt => "URL prefix to link git branches to", :default => ""
53
+ end
54
+
55
+ class ScreenView
56
+ add_to_view :issue_summary do |issue, config|
57
+ " Git branch: #{issue.git_branch || 'none'}\n"
58
+ end
59
+
60
+ add_to_view :issue_details do |issue, config|
61
+ commits = issue.git_commits[0...5]
62
+ next if commits.empty?
63
+ "Recent commits:\n" + commits.map do |date, email, hash, msg|
64
+ "- #{msg} [#{hash}] (#{email.shortened_email}, #{date.ago} ago)\n"
65
+ end.join + "\n"
66
+ end
67
+ end
68
+
69
+ class HtmlView
70
+ add_to_view :issue_summary do |issue, config|
71
+ next unless issue.git_branch
72
+ [<<EOS, { :issue => issue, :url_prefix => config.git_branch_url_prefix }]
73
+ <tr>
74
+ <td class='attrname'>Git branch:</td>
75
+ <td class='attrval'><%= url_prefix && !url_prefix.blank? ? link_to([url_prefix, issue.git_branch].join, issue.git_branch) : h(issue.git_branch) %></td>
76
+ </tr>
77
+ EOS
78
+ end
79
+
80
+ add_to_view :issue_details do |issue, config|
81
+ commits = issue.git_commits
82
+ next if commits.empty?
83
+
84
+ [<<EOS, { :commits => commits, :url_prefix => config.git_commit_url_prefix }]
85
+ <h2>Commits for this issue</h2>
86
+ <table class="log">
87
+ <% commits.each_with_index do |(time, who, hash, msg), i| %>
88
+ <tr class="<%= i % 2 == 0 ? "even-row" : "odd-row" %>">
89
+ <td class="time"><%=t time %></td>
90
+ <td class="person"><%=obscured_email who %></td>
91
+ <td class="message"><%=h msg %> [<%= url_prefix && !url_prefix.blank? ? link_to([url_prefix, hash].join, hash) : hash %>]</td>
92
+ </tr>
93
+ <tr><td></td></tr>
94
+ <% end %>
95
+ </table>
96
+ EOS
97
+ end
98
+ end
99
+
100
+ class Operator
101
+ operation :set_branch, "Set the git feature branch of an issue", :issue, :maybe_string
102
+ def set_branch project, config, issue, maybe_string
103
+ puts "Issue #{issue.name} currently " + if issue.git_branch
104
+ "assigned to git branch #{issue.git_branch.inspect}."
105
+ else
106
+ "not assigned to any git branch."
107
+ end
108
+
109
+ branch = maybe_string || ask("Git feature branch name:")
110
+ return unless branch
111
+
112
+ if branch == issue.git_branch
113
+ raise Error, "issue #{issue.name} already assigned to branch #{issue.git_branch.inspect}"
114
+ end
115
+
116
+ puts "Assigning to branch #{branch.inspect}."
117
+ issue.git_branch = branch
118
+ end
119
+
120
+ operation :commit, "Runs git-commit and auto-fills the issue name in the commit message", :issue do
121
+ opt :all, "commit all changed files", :short => "-a", :default => false
122
+ opt :verbose, "show diff between HEAD and what would be committed", \
123
+ :short => "-v", :default => false
124
+ opt :message, "Use the given <s> as the commit message.", \
125
+ :short => "-m", :type => :string
126
+ opt :edit, "Further edit the message, even if --message is given.", :short => "-e", :default => false
127
+ end
128
+
129
+ def commit project, config, opts, issue
130
+ opts[:edit] = true if opts[:message].nil?
131
+
132
+ args = {
133
+ :verbose => "--verbose",
134
+ :all => "--all",
135
+ :edit => "--edit",
136
+ }.map { |k, v| opts[k] ? v : "" }.join(" ")
137
+
138
+ comment = "# #{issue.name}: #{issue.title}"
139
+ tag = "DitzStr-issue: #{issue.id}"
140
+ message = if opts[:message] && !opts[:edit]
141
+ "#{opts[:message]}\n\n#{tag}"
142
+ elsif opts[:message] && opts[:edit]
143
+ "#{opts[:message]}\n\n#{comment}\n#{tag}"
144
+ else
145
+ "#{comment}\n#{tag}"
146
+ end
147
+
148
+ message = message.gsub("\"", "\\\"")
149
+ exec "git commit #{args} --message=\"#{message}\""
150
+ end
151
+ end
152
+
153
+ end
@@ -0,0 +1,174 @@
1
+ ## issue-claiming ditz plugin
2
+ ##
3
+ ## This plugin allows people to claim issues. This is useful for avoiding
4
+ ## duplication of work---you can check to see if someone's claimed an
5
+ ## issue before starting to work on it, and you can let people know what
6
+ ## you're working on.
7
+ ##
8
+ ## Commands added:
9
+ ## ditz claim: claim an issue for yourself
10
+ ## ditz unclaim: unclaim a claimed issue
11
+ ## ditz mine: show all issues claimed by you
12
+ ## ditz claimed: show all claimed issues, by developer
13
+ ## ditz unclaimed: show all unclaimed issues
14
+ ##
15
+ ## Usage:
16
+ ## 1. add a line "- issue-claiming" to the .ditz-plugins file in the project
17
+ ## root
18
+ ## 2. use the above commands to abandon
19
+
20
+ module DitzStr
21
+ class Issue
22
+ field :claimer, :ask => false
23
+
24
+ def claim who, comment, force=false
25
+ raise Error, "already claimed by #{claimer}" if claimer && !force
26
+ log "issue claimed", who, comment
27
+ self.claimer = who
28
+ end
29
+
30
+ def unclaim who, comment, force=false
31
+ raise Error, "not claimed" unless claimer
32
+ raise Error, "can only be unclaimed by #{claimer}" unless claimer == who || force
33
+ if claimer == who
34
+ log "issue unclaimed", who, comment
35
+ else
36
+ log "unassigned from #{claimer}", who, comment
37
+ end
38
+ self.claimer = nil
39
+ end
40
+
41
+ def claimed?; claimer end
42
+ def unclaimed?; !claimed? end
43
+ end
44
+
45
+ class ScreenView
46
+ add_to_view :issue_summary do |issue, config|
47
+ " Claimed by: #{issue.claimer || 'none'}\n"
48
+ end
49
+ end
50
+
51
+ class HtmlView
52
+ add_to_view :issue_summary do |issue, config|
53
+ next unless issue.claimer
54
+ [<<EOS, { :issue => issue }]
55
+ <tr>
56
+ <td class='attrname'>Claimed by:</td>
57
+ <td class='attrval'><%= h(issue.claimer) %></td>
58
+ </tr>
59
+ EOS
60
+ end
61
+ end
62
+
63
+ class Operator
64
+ alias :__issue_claiming_start :start
65
+ def start project, config, opts, issue
66
+ if issue.claimed? && issue.claimer != config.user
67
+ raise Error, "issue #{issue.name} claimed by #{issue.claimer}"
68
+ else
69
+ __issue_claiming_start project, config, opts, issue
70
+ end
71
+ end
72
+
73
+ alias :__issue_claiming_stop :stop
74
+ def stop project, config, opts, issue
75
+ if issue.claimed? && issue.claimer != config.user
76
+ raise Error, "issue #{issue.name} claimed by #{issue.claimer}"
77
+ else
78
+ __issue_claiming_stop project, config, opts, issue
79
+ end
80
+ end
81
+
82
+ alias :__issue_claiming_close :close
83
+ def close project, config, opts, issue
84
+ if issue.claimed? && issue.claimer != config.user
85
+ raise Error, "issue #{issue.name} claimed by #{issue.claimer}"
86
+ else
87
+ __issue_claiming_close project, config, opts, issue
88
+ end
89
+ end
90
+
91
+ operation :claim, "Claim an issue for yourself", :issue do
92
+ opt :force, "Claim this issue even if someone else has claimed it", :default => false
93
+ end
94
+ def claim project, config, opts, issue
95
+ puts "Claiming issue #{issue.name}: #{issue.title}."
96
+ comment = ask_multiline "Comments" unless $opts[:no_comment]
97
+ issue.claim config.user, comment, opts[:force]
98
+ puts "Issue #{issue.name} marked as claimed by #{config.user}"
99
+ end
100
+
101
+ operation :unclaim, "Unclaim a claimed issue", :issue do
102
+ opt :force, "Unclaim this issue even if it's claimed by someone else", :default => false
103
+ end
104
+ def unclaim project, config, opts, issue
105
+ puts "Unclaiming issue #{issue.name}: #{issue.title}."
106
+ comment = ask_multiline "Comments" unless $opts[:no_comment]
107
+ issue.unclaim config.user, comment, opts[:force]
108
+ puts "Issue #{issue.name} marked as unclaimed."
109
+ end
110
+
111
+ operation :mine, "Show all issues claimed by you", :maybe_release do
112
+ opt :all, "Show all issues, not just open ones"
113
+ end
114
+ def mine project, config, opts, releases
115
+ releases ||= project.unreleased_releases + [:unassigned]
116
+ releases = [*releases]
117
+
118
+ issues = project.issues.select do |i|
119
+ r = project.release_for(i.release) || :unassigned
120
+ releases.member?(r) && i.claimer == config.user && (opts[:all] || i.open?)
121
+ end
122
+ if issues.empty?
123
+ puts "No issues."
124
+ else
125
+ print_todo_list_by_release_for project, issues
126
+ end
127
+ end
128
+
129
+ operation :claimed, "Show claimed issues by claimer", :maybe_release do
130
+ opt :all, "Show all issues, not just open ones"
131
+ end
132
+ def claimed project, config, opts, releases
133
+ releases ||= project.unreleased_releases + [:unassigned]
134
+ releases = [*releases]
135
+
136
+ issues = project.issues.inject({}) do |h, i|
137
+ r = project.release_for(i.release) || :unassigned
138
+ if i.claimed? && (opts[:all] || i.open?) && releases.member?(r)
139
+ (h[i.claimer] ||= []) << i
140
+ end
141
+ h
142
+ end
143
+
144
+ if issues.empty?
145
+ puts "No issues."
146
+ else
147
+ issues.keys.sort.each do |c|
148
+ puts "#{c}:"
149
+ puts todo_list_for(issues[c], :show_release => true)
150
+ puts
151
+ end
152
+ end
153
+ end
154
+
155
+ operation :unclaimed, "Show all unclaimed issues", :maybe_release do
156
+ opt :all, "Show all issues, not just open ones"
157
+ end
158
+ def unclaimed project, config, opts, releases
159
+ releases ||= project.unreleased_releases + [:unassigned]
160
+ releases = [*releases]
161
+
162
+ issues = project.issues.select do |i|
163
+ r = project.release_for(i.release) || :unassigned
164
+ releases.member?(r) && i.claimer.nil? && (opts[:all] || i.open?)
165
+ end
166
+ if issues.empty?
167
+ puts "No issues."
168
+ else
169
+ print_todo_list_by_release_for project, issues
170
+ end
171
+ end
172
+ end
173
+
174
+ end
Binary file
@@ -0,0 +1,111 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4
+
5
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
6
+ <head>
7
+ <title><%= project.name %> release <%= release.name %></title>
8
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
9
+ <link rel="stylesheet" href="style.css" type="text/css" />
10
+ <link rel="stylesheet" href="dropdown.css" type="text/css"/>
11
+ <script type="text/javascript" src="dropdown.js">
12
+ </script>
13
+ </head>
14
+ <body>
15
+
16
+ <div class="main">
17
+
18
+ <h1><%= project.name %> release <%= release.name %></h1>
19
+ <div class="menu">
20
+ <% if not brickargs.empty? %>
21
+ <dl class="dropdown">
22
+ <%= "<dt id=\"newissue-ddheader\" onmouseover=\"ddMenu('newissue',1)\" onmouseout=\"ddMenu('newissue',-1)\" onclick=\"location.href='/new_issue.html?release=#{release.name}'\" >New Issue</dt>" %>
23
+ </dl>
24
+ <% end %>
25
+ </div>
26
+ <br/><br/><br/>
27
+
28
+ <div class="backptr"><%= link_to "index", "&laquo; #{project.name} project page" %></div>
29
+
30
+ <table>
31
+ <tbody>
32
+ <tr>
33
+ <td class="attrname">Status:</td>
34
+ <td class="attrval"><%= release.status %></td>
35
+ </tr>
36
+ <% if release.released? %>
37
+ <tr>
38
+ <td class="attrname">Release time:</td>
39
+ <td class="attrval"><%=t release.release_time %></td>
40
+ </tr>
41
+ <% end %>
42
+ <tr>
43
+ <td class="attrname">Completion:</td>
44
+ <td>
45
+ <%
46
+ num_done = issues.count_of { |i| i.closed? }
47
+ pct_done = issues.size == 0 ? 1.0 : (num_done.to_f / issues.size.to_f)
48
+ %>
49
+ <%= progress_meter pct_done %>
50
+ <%= sprintf "%.0f%%", pct_done * 100.0 %>
51
+ </td>
52
+ </tr>
53
+ <tr><td></td><td class="attrval">
54
+ <%= num_done %> / <%= issues.size %> issues
55
+ </td></tr>
56
+ </tbody>
57
+ </table>
58
+
59
+ <h2>Issues</h2>
60
+ <% if issues.empty? %>
61
+ <p>No issues assigned to this release.</p>
62
+ <% else %>
63
+ <%= render "issue_table", :show_component => false, :show_release => false %>
64
+ <% end %>
65
+
66
+ <h2>Recent activity for this release</h2>
67
+ <table class="log">
68
+ <tbody>
69
+ <% issues.map { |i| i.log_events.map { |e| [e, i] } }.
70
+ flatten_one_level.
71
+ sort_by { |e| e.first.first }.
72
+ reverse[0 ... 10].
73
+ each_with_index do |((date, who, what, comment), i), idx| %>
74
+ <tr class="<%= idx % 2 == 0 ? "even-row" : "odd-row" %>">
75
+ <td class="date"><%= date.pretty_date %></td>
76
+ <td class="issuename">
77
+ <%= issue_link_for i, :status_image => true %>
78
+ </td>
79
+ <td> <%= what %> </td>
80
+ </tr>
81
+ <tr><td></td></tr>
82
+ <% end %>
83
+ </tbody>
84
+ </table>
85
+
86
+ <h2>Release log</h2>
87
+ <table class="log">
88
+ <tbody>
89
+ <% release.log_events.reverse.each_with_index do |(time, who, what, comment), i| %>
90
+ <tr class="<%= i % 2 == 0 ? "even-row" : "odd-row" %>">
91
+ <td class="date"><%=h time %></td>
92
+ <td class="person"><%=obscured_email who %></td>
93
+ <td class="message"><%=h what %></td>
94
+ </tr>
95
+ <tr><td colspan="3" class="logcomment">
96
+ <% if comment.empty? %>
97
+ <% else %>
98
+ <%= link_issue_names project, comment %>
99
+ <% end %>
100
+ </td></tr>
101
+ <tr><td></td></tr>
102
+ <% end %>
103
+ </tbody>
104
+ </table>
105
+
106
+ </div>
107
+
108
+ <div class="footer">Generated by <a href="http://ditz.rubyforge.org/">ditz</a>.</div>
109
+
110
+ </body>
111
+ </html>