ursm-ditz 0.4 → 0.5

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 (50) hide show
  1. data/Changelog +15 -0
  2. data/INSTALL +20 -0
  3. data/LICENSE +674 -0
  4. data/Manifest.txt +40 -0
  5. data/PLUGINS.txt +140 -0
  6. data/README.txt +43 -27
  7. data/Rakefile +36 -3
  8. data/ReleaseNotes +6 -0
  9. data/bin/ditz +49 -63
  10. data/contrib/completion/ditz.bash +25 -9
  11. data/lib/ditz.rb +52 -7
  12. data/lib/ditz/file-storage.rb +54 -0
  13. data/lib/{hook.rb → ditz/hook.rb} +1 -1
  14. data/lib/{html.rb → ditz/html.rb} +42 -4
  15. data/lib/{lowline.rb → ditz/lowline.rb} +31 -11
  16. data/lib/{model-objects.rb → ditz/model-objects.rb} +53 -21
  17. data/lib/ditz/model.rb +321 -0
  18. data/lib/{operator.rb → ditz/operator.rb} +122 -67
  19. data/lib/ditz/plugins/git-sync.rb +83 -0
  20. data/lib/{plugins → ditz/plugins}/git.rb +57 -18
  21. data/lib/ditz/plugins/issue-claiming.rb +174 -0
  22. data/lib/ditz/plugins/issue-labeling.rb +161 -0
  23. data/lib/{util.rb → ditz/util.rb} +4 -0
  24. data/lib/{view.rb → ditz/view.rb} +0 -0
  25. data/lib/{views.rb → ditz/views.rb} +7 -4
  26. data/man/{ditz.1 → man1/ditz.1} +1 -1
  27. data/setup.rb +1585 -0
  28. data/share/ditz/blue-check.png +0 -0
  29. data/{lib → share/ditz}/component.rhtml +7 -5
  30. data/share/ditz/green-bar.png +0 -0
  31. data/share/ditz/green-check.png +0 -0
  32. data/share/ditz/index.rhtml +130 -0
  33. data/share/ditz/issue.rhtml +119 -0
  34. data/share/ditz/issue_table.rhtml +28 -0
  35. data/share/ditz/red-check.png +0 -0
  36. data/share/ditz/release.rhtml +98 -0
  37. data/share/ditz/style.css +226 -0
  38. data/share/ditz/unassigned.rhtml +23 -0
  39. data/share/ditz/yellow-bar.png +0 -0
  40. metadata +50 -28
  41. data/lib/index.rhtml +0 -113
  42. data/lib/issue.rhtml +0 -111
  43. data/lib/issue_table.rhtml +0 -33
  44. data/lib/model.rb +0 -208
  45. data/lib/plugins/issue-claiming.rb +0 -92
  46. data/lib/release.rhtml +0 -69
  47. data/lib/style.css +0 -127
  48. data/lib/trollop.rb +0 -518
  49. data/lib/unassigned.rhtml +0 -31
  50. data/lib/vendor/yaml_waml.rb +0 -28
@@ -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 Ditz
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
@@ -1,3 +1,31 @@
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
+
1
29
  require 'time'
2
30
 
3
31
  module Ditz
@@ -11,7 +39,7 @@ class Issue
11
39
  filters << "master..#{git_branch}" if git_branch
12
40
 
13
41
  output = filters.map do |f|
14
- `git log --pretty=format:\"%aD\t%an <%ae>\t%h\t%s\" #{f}`
42
+ `git log --pretty=format:\"%aD\t%an <%ae>\t%h\t%s\" #{f} 2> /dev/null`
15
43
  end.join
16
44
 
17
45
  @git_commits = output.split(/\n/).map { |l| l.split("\t") }.
@@ -55,17 +83,14 @@ EOS
55
83
 
56
84
  [<<EOS, { :commits => commits, :url_prefix => config.git_commit_url_prefix }]
57
85
  <h2>Commits for this issue</h2>
58
- <table>
86
+ <table class="log">
59
87
  <% commits.each_with_index do |(time, who, hash, msg), i| %>
60
- <% if i % 2 == 0 %>
61
- <tr class="logentryeven">
62
- <% else %>
63
- <tr class="logentryodd">
64
- <% end %>
65
- <td class="logtime"><%=t time %></td>
66
- <td class="logwho"><%=obscured_email who %></td>
67
- <td class="logwhat"><%=h msg %> [<%= url_prefix && !url_prefix.blank? ? link_to([url_prefix, hash].join, hash) : hash %>]</td>
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>
68
92
  </tr>
93
+ <tr><td></td></tr>
69
94
  <% end %>
70
95
  </table>
71
96
  EOS
@@ -98,16 +123,30 @@ class Operator
98
123
  :short => "-v", :default => false
99
124
  opt :message, "Use the given <s> as the commit message.", \
100
125
  :short => "-m", :type => :string
126
+ opt :edit, "Further edit the message, even if --message is given.", :short => "-e", :default => false
101
127
  end
128
+
102
129
  def commit project, config, opts, issue
103
- verbose_flag = opts[:verbose] ? "--verbose" : ""
104
- all_flag = opts[:all] ? "--all" : ""
105
- ditz_header = "Ditz-issue: #{issue.id}"
106
- message = opts[:message] ? "#{opts[:message]}\n\n#{ditz_header}" : \
107
- "#{ditz_header}"
108
- edit_flag = opts[:message] ? "" : "--edit"
109
- message_flag = %{--message="#{message}"}
110
- exec "git commit #{all_flag} #{verbose_flag} #{message_flag} #{edit_flag}"
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 = "Ditz-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}\""
111
150
  end
112
151
  end
113
152
 
@@ -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 Ditz
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
@@ -0,0 +1,161 @@
1
+ ## issue-labeling ditz plugin
2
+ ##
3
+ ## This plugin allows label issues. This can replace the issue component
4
+ ## and/or issue types (bug,feature,task), by providing a more flexible
5
+ ## to organize your issues.
6
+ ##
7
+ ## Commands added:
8
+ ## ditz new_label [label]: create a new label for the project
9
+ ## ditz label <issue> <labels>: label an issue with some labels
10
+ ## ditz unlabel <issue> [labels]: remove some label(s) of an issue
11
+ ## ditz labeled <labels> [release]: show all issues with these labels
12
+ ##
13
+ ## Usage:
14
+ ## 1. add a line "- issue-labeling" to the .ditz-plugins file in the project
15
+ ## root
16
+ ## 2. use the above commands to abandon
17
+
18
+ # TODO:
19
+ # * extend the HTML view to have per-labels listings
20
+ # * allow for more compact way to type them (completion, prefixes...)
21
+
22
+ module Ditz
23
+
24
+ class Label < ModelObject
25
+ include Comparable
26
+ field :name
27
+
28
+ def name_prefix; @name.gsub(/\s+/, "-").downcase end
29
+
30
+ def <=> x ; name <=> x.name end
31
+ def == x ; name == x.name end
32
+
33
+ end # class Label
34
+
35
+ class Project
36
+ field :labels, :multi => true, :interactive_generator => :get_labels
37
+
38
+ def get_labels
39
+ lab_names = ask_for_many("labels")
40
+ ([name] + lab_names).uniq.map { |n| Label.create_interactively :with => { :name => n } }
41
+ end
42
+
43
+ def label_for label_name
44
+ labels.find { |i| i.name == label_name }
45
+ end
46
+
47
+ def labels_for label_names
48
+ label_names.split(/\s*,\s*/).map do |val|
49
+ label_for(val) or raise Error, "no label with name #{val}"
50
+ end
51
+ end
52
+
53
+ end # class Project
54
+
55
+ class Issue
56
+ field :labels, :multi => true, :interactive_generator => :get_labels
57
+
58
+ def get_labels config, project
59
+ ask_for_selection(project.labels, "label", :name, true).map {|x|x.name}
60
+ end
61
+
62
+ def apply_labels new_labels, who, comment
63
+ log "issue labeled", who, comment
64
+ new_labels.each { |l| add_label l }
65
+ end
66
+
67
+ def remove_labels labels_to_remove, who, comment
68
+ log "issue unlabeled", who, comment
69
+ if labels_to_remove.nil?
70
+ self.labels = []
71
+ else
72
+ labels_to_remove.each { |l| drop_label l }
73
+ end
74
+ end
75
+
76
+ def labeled? label=nil; (label.nil?)? !labels.empty? : labels.include?(label) end
77
+ def unlabeled? label=nil; !labeled?(label) end
78
+ end
79
+
80
+ class ScreenView
81
+ add_to_view :issue_summary do |issue, config|
82
+ " Labels: #{(issue.labeled?)? issue.labels.map{|l|l.name}.join(', ') : 'none'}\n"
83
+ end
84
+ end
85
+
86
+ class HtmlView
87
+ add_to_view :issue_summary do |issue, config|
88
+ next if issue.unlabeled?
89
+ [<<EOS, { :issue => issue }]
90
+ <tr>
91
+ <td class='attrname'>Labels:</td>
92
+ <td class='attrval'><%= h(issue.labels.map{|l|l.name}.join(', ')) %></td>
93
+ </tr>
94
+ EOS
95
+ end
96
+ end
97
+
98
+ class Operator
99
+
100
+ operation :new_label, "Create a new label for the project", :maybe_label
101
+ def new_label project, config, maybe_label
102
+ puts "Adding label #{maybe_label}." if maybe_label
103
+ label = Label.create_interactively(:args => [project, config], :with => { :name => maybe_label }) or return
104
+ project.add_label label
105
+ puts "Added label #{label.name}."
106
+ end
107
+
108
+ operation :label, "Apply labels to an issue", :issue, :labels
109
+ def label project, config, issue, label_names
110
+ labels = project.labels_for label_names
111
+ puts "Adding labels #{label_names} to issue #{issue.name}: #{issue.title}."
112
+ comment = ask_multiline "Comments" unless $opts[:no_comment]
113
+ issue.apply_labels labels, config.user, comment
114
+ show_labels issue
115
+ end
116
+
117
+ operation :unlabel, "Remove some labels of an issue", :issue, :maybe_labels
118
+ def unlabel project, config, issue, label_names
119
+ labels = if label_names
120
+ puts "Removing #{label_names} labels from issue #{issue.name}: #{issue.title}."
121
+ project.labels_for label_names
122
+ else
123
+ puts "Removing labels from issue #{issue.name}: #{issue.title}."
124
+ nil
125
+ end
126
+ comment = ask_multiline "Comments" unless $opts[:no_comment]
127
+ issue.remove_labels labels, config.user, comment
128
+ show_labels issue
129
+ end
130
+
131
+ def show_labels issue
132
+ if issue.labeled?
133
+ puts "Issue #{issue.name} is now labeled with #{issue.labels.map{|l|l.name}.join(', ')}"
134
+ else
135
+ puts "Issue #{issue.name} is now unlabeled"
136
+ end
137
+ end
138
+ private :show_labels
139
+
140
+ operation :labeled, "Show labeled issues", :labels, :maybe_release do
141
+ opt :all, "Show all issues, not just open ones"
142
+ end
143
+ def labeled project, config, opts, labels, releases
144
+ releases ||= project.unreleased_releases + [:unassigned]
145
+ releases = [*releases]
146
+ labels = project.labels_for labels
147
+
148
+ issues = project.issues.select do |i|
149
+ r = project.release_for(i.release) || :unassigned
150
+ labels.all? { |l| i.labeled? l } && (opts[:all] || i.open?) && releases.member?(r)
151
+ end
152
+
153
+ if issues.empty?
154
+ puts "No issues."
155
+ else
156
+ print_todo_list_by_release_for project, issues
157
+ end
158
+ end
159
+ end
160
+
161
+ end