ditz 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,101 @@
1
+ require 'time'
2
+
3
+ module Ditz
4
+ class Issue
5
+ field :git_branch, :ask => false
6
+
7
+ def git_commits
8
+ return @git_commits if @git_commits
9
+
10
+ filters = ["--grep=\"Ditz-issue: #{id}\""]
11
+ filters << "master..#{git_branch}" if git_branch
12
+
13
+ output = filters.map do |f|
14
+ `git log --pretty=format:\"%aD\t%an <%ae>\t%h\t%s\" #{f}`
15
+ end.join
16
+
17
+ @git_commits = output.split(/\n/).map { |l| l.split("\t") }.
18
+ map { |date, email, hash, msg| [Time.parse(date).utc, email, hash, msg] }
19
+ end
20
+ end
21
+
22
+ class Config
23
+ field :git_commit_url_prefix, :prompt => "URL prefix (if any) to link git commits to"
24
+ field :git_branch_url_prefix, :prompt => "URL prefix (if any) to link git branches to"
25
+ end
26
+
27
+ class ScreenView
28
+ add_to_view :issue_summary do |issue, config|
29
+ " Git branch: #{issue.git_branch || 'none'}\n"
30
+ end
31
+
32
+ add_to_view :issue_details do |issue, config|
33
+ commits = issue.git_commits[0...5]
34
+ next if commits.empty?
35
+ "Recent commits:\n" + commits.map do |date, email, hash, msg|
36
+ "- #{msg} [#{hash}] (#{email.shortened_email}, #{date.ago} ago)\n"
37
+ end.join + "\n"
38
+ end
39
+ end
40
+
41
+ class HtmlView
42
+ add_to_view :issue_summary do |issue, config|
43
+ next unless issue.git_branch
44
+ [<<EOS, { :issue => issue, :url_prefix => config.git_branch_url_prefix }]
45
+ <tr>
46
+ <td class='attrname'>Git branch:</td>
47
+ <td class='attrval'><%= url_prefix ? link_to([url_prefix, issue.git_branch].join, issue.git_branch) : h(issue.git_branch) %></td>
48
+ </tr>
49
+ EOS
50
+ end
51
+
52
+ add_to_view :issue_details do |issue, config|
53
+ commits = issue.git_commits
54
+ next if commits.empty?
55
+
56
+ [<<EOS, { :commits => commits, :url_prefix => config.git_commit_url_prefix }]
57
+ <h2>Commits for this issue</h2>
58
+ <table>
59
+ <% 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 ? link_to([url_prefix, hash].join, hash) : hash %>]</td>
68
+ </tr>
69
+ <% end %>
70
+ </table>
71
+ EOS
72
+ end
73
+ end
74
+
75
+ class Operator
76
+ operation :set_branch, "Set the git feature branch of an issue", :issue, :maybe_string
77
+ def set_branch project, config, issue, maybe_string
78
+ puts "Issue #{issue.name} currently " + if issue.git_branch
79
+ "assigned to git branch #{issue.git_branch.inspect}."
80
+ else
81
+ "not assigned to any git branch."
82
+ end
83
+
84
+ branch = maybe_string || ask("Git feature branch name:")
85
+ return unless branch
86
+
87
+ if branch == issue.git_branch
88
+ raise Error, "issue #{issue.name} already assigned to branch #{issue.git_branch.inspect}"
89
+ end
90
+
91
+ puts "Assigning to branch #{branch.inspect}."
92
+ issue.git_branch = branch
93
+ end
94
+
95
+ operation :commit, "Runs git-commit and auto-fills the issue name in the commit message", :issue
96
+ def commit project, config, issue
97
+ exec "git commit -v -m \"Ditz-issue: #{issue.id}\" -e"
98
+ end
99
+ end
100
+
101
+ end
@@ -7,11 +7,17 @@ body {
7
7
  width: 90%;
8
8
  }
9
9
 
10
+ div.wrapper {
11
+ }
12
+
13
+ div.footer {
14
+ }
15
+
10
16
  a, a:visited {
11
17
  border-bottom: 1px dotted #03c;
12
18
  background: inherit;
13
- color: #03c;
14
19
  text-decoration: none;
20
+ color: #03c;
15
21
  }
16
22
 
17
23
  ul {
@@ -23,6 +29,20 @@ p {
23
29
  width: 40em;
24
30
  }
25
31
 
32
+ .dimmed {
33
+ color: #bbb;
34
+ }
35
+
36
+ div.on-the-left {
37
+ float: left;
38
+ width: 39%;
39
+ }
40
+
41
+ div.on-the-right {
42
+ float: right;
43
+ width: 59%;
44
+ }
45
+
26
46
  table {
27
47
  border-style: none;
28
48
  border-spacing: 0;
@@ -70,8 +90,23 @@ h1 {
70
90
  text-align: center;
71
91
  }
72
92
 
93
+ .issuestatus_closed a {
94
+ color: #202020;
95
+ }
96
+
97
+ .issuestatus_in_progress a {
98
+ color: #202020;
99
+ }
100
+
101
+ .issuestatus_paused a {
102
+ color: #202020;
103
+ }
104
+
105
+ .issuestatus_unstarted a {
106
+ color: #202020;
107
+ }
108
+
73
109
  .logwhat {
74
- font-style: italic;
75
110
  }
76
111
 
77
112
  .logentryeven {
@@ -86,6 +121,7 @@ h1 {
86
121
  padding-left: 3em;
87
122
  }
88
123
 
89
- .footer {
124
+ p.footer {
90
125
  font-size: small;
91
126
  }
127
+
@@ -15,7 +15,7 @@
15
15
  <%= i.status_string %><% if i.closed? %>: <%= i.disposition_string %><% end %>
16
16
  </td>
17
17
  <td class="issuename">
18
- <%= link_to i, i.title %>
18
+ <%= fancy_issue_link_for i %>
19
19
  <%= i.bug? ? '(bug)' : '' %>
20
20
  </td>
21
21
  </tr>
@@ -8,6 +8,10 @@ module Enumerable
8
8
  def min_of(&b); map(&b).min end
9
9
  end
10
10
 
11
+ class Array
12
+ def uniq_by; inject({}) { |h, o| h[yield(o)] = o; h }.values end
13
+ end
14
+
11
15
  module Enumerable
12
16
  def map_with_index # sigh...
13
17
  ret = []
@@ -50,3 +54,4 @@ class Array
50
54
  end
51
55
  end
52
56
  end
57
+
@@ -0,0 +1,16 @@
1
+ module Ditz
2
+
3
+ class View
4
+ def self.add_to_view type, &block
5
+ @views ||= {}
6
+ @views[type] ||= []
7
+ @views[type] << block
8
+ end
9
+
10
+ def self.view_additions_for type
11
+ @views ||= {}
12
+ @views[type] || []
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,136 @@
1
+ require 'view'
2
+ require 'html'
3
+
4
+ module Ditz
5
+
6
+ class ScreenView < View
7
+ def initialize project, config, device=$stdout
8
+ @device = device
9
+ @config = config
10
+ end
11
+
12
+ def format_log_events events
13
+ return "none" if events.empty?
14
+ events.reverse.map do |time, who, what, comment|
15
+ "- #{what} (#{who.shortened_email}, #{time.ago} ago)" +
16
+ (comment =~ /\S/ ? "\n" + comment.gsub(/^/, " > ") : "")
17
+ end.join("\n")
18
+ end
19
+ private :format_log_events
20
+
21
+ def render_issue issue
22
+ status = case issue.status
23
+ when :closed
24
+ "#{issue.status_string}: #{issue.disposition_string}"
25
+ else
26
+ issue.status_string
27
+ end
28
+ desc = if issue.desc.size < 80 - "Description: ".length
29
+ issue.desc
30
+ else
31
+ "\n" + issue.desc.gsub(/^/, " ") + "\n"
32
+ end
33
+ @device.puts <<EOS
34
+ #{"Issue #{issue.name}".underline}
35
+ Title: #{issue.title}
36
+ Description: #{desc}
37
+ Type: #{issue.type}
38
+ Status: #{status}
39
+ Creator: #{issue.reporter}
40
+ Age: #{issue.creation_time.ago}
41
+ Release: #{issue.release}
42
+ References: #{issue.references.listify " "}
43
+ Identifier: #{issue.id}
44
+ EOS
45
+
46
+ self.class.view_additions_for(:issue_summary).each { |b| @device.print(b[issue, @config] || next) }
47
+ puts
48
+ self.class.view_additions_for(:issue_details).each { |b| @device.print(b[issue, @config] || next) }
49
+
50
+ @device.puts <<EOS
51
+ Event log:
52
+ #{format_log_events issue.log_events}
53
+ EOS
54
+ end
55
+ end
56
+
57
+ class HtmlView < View
58
+ def initialize project, config, dir
59
+ @project = project
60
+ @config = config
61
+ @dir = dir
62
+ @template_dir = File.dirname Ditz::find_ditz_file("index.rhtml")
63
+ end
64
+
65
+ def render_all
66
+ Dir.mkdir @dir unless File.exists? @dir
67
+ FileUtils.cp File.join(@template_dir, "style.css"), @dir
68
+
69
+ ## build up links
70
+ links = {}
71
+ @project.releases.each { |r| links[r] = "release-#{r.name}.html" }
72
+ @project.issues.each { |i| links[i] = "issue-#{i.id}.html" }
73
+ @project.components.each { |c| links[c] = "component-#{c.name}.html" }
74
+ links["unassigned"] = "unassigned.html" # special case: unassigned
75
+ links["index"] = "index.html" # special case: index
76
+
77
+ @project.issues.each do |issue|
78
+ fn = File.join @dir, links[issue]
79
+ #puts "Generating #{fn}..."
80
+
81
+ extra_summary = self.class.view_additions_for(:issue_summary).map { |b| b[issue, @config] }.compact
82
+ extra_details = self.class.view_additions_for(:issue_details).map { |b| b[issue, @config] }.compact
83
+
84
+ erb = ErbHtml.new(@template_dir, links, :issue => issue,
85
+ :release => (issue.release ? @project.release_for(issue.release) : nil),
86
+ :component => @project.component_for(issue.component),
87
+ :project => @project)
88
+
89
+ extra_summary_html = extra_summary.map { |string, extra_binding| erb.render_string string, extra_binding }.join
90
+ extra_details_html = extra_details.map { |string, extra_binding| erb.render_string string, extra_binding }.join
91
+
92
+ File.open(fn, "w") { |f| f.puts erb.render_template("issue", { :extra_summary_html => extra_summary_html, :extra_details_html => extra_details_html }) }
93
+ end
94
+
95
+ @project.releases.each do |r|
96
+ fn = File.join @dir, links[r]
97
+ #puts "Generating #{fn}..."
98
+ File.open(fn, "w") do |f|
99
+ f.puts ErbHtml.new(@template_dir, links, :release => r,
100
+ :issues => @project.issues_for_release(r), :project => @project).
101
+ render_template("release")
102
+ end
103
+ end
104
+
105
+ @project.components.each do |c|
106
+ fn = File.join @dir, links[c]
107
+ #puts "Generating #{fn}..."
108
+ File.open(fn, "w") do |f|
109
+ f.puts ErbHtml.new(@template_dir, links, :component => c,
110
+ :issues => @project.issues_for_component(c), :project => @project).
111
+ render_template("component")
112
+ end
113
+ end
114
+
115
+ fn = File.join @dir, links["unassigned"]
116
+ #puts "Generating #{fn}..."
117
+ File.open(fn, "w") do |f|
118
+ f.puts ErbHtml.new(@template_dir, links,
119
+ :issues => @project.unassigned_issues, :project => @project).
120
+ render_template("unassigned")
121
+ end
122
+
123
+ past_rels, upcoming_rels = @project.releases.partition { |r| r.released? }
124
+ fn = File.join @dir, links["index"]
125
+ #puts "Generating #{fn}..."
126
+ File.open(fn, "w") do |f|
127
+ f.puts ErbHtml.new(@template_dir, links, :project => @project,
128
+ :past_releases => past_rels, :upcoming_releases => upcoming_rels,
129
+ :components => @project.components).
130
+ render_template("index")
131
+ end
132
+ puts "Local generated URL: file://#{File.expand_path(fn)}"
133
+ end
134
+ end
135
+
136
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ditz
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.3"
4
+ version: "0.4"
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Morgan
@@ -9,23 +9,33 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-06-04 07:00:00 Z
12
+ date: 2008-07-27 07:00:00 Z
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: trollop
17
+ type: :runtime
17
18
  version_requirement:
18
19
  version_requirements: !ruby/object:Gem::Requirement
19
20
  requirements:
20
21
  - - ">="
21
22
  - !ruby/object:Gem::Version
22
- version: "1.7"
23
+ version: 1.8.2
23
24
  version:
24
- description: "Ditz is a simple, light-weight distributed issue tracker designed to work with distributed version control systems like darcs and git. Ditz maintains an issue database directory on disk, with files written in a line-based and human-editable format. This directory is kept under version control alongside project code. Changes in issue state is handled by version control like code change: included as part of a commit, merged with changes from other developers, conflict-resolved in the standard manner, etc. Ditz provides a simple, console-based interface for creating and updating the issue database files, and some rudimentary HTML generation capabilities for producing world-readable status pages. It offers no central public method of bug submission. Synopsis: # set up project. creates the bugs.yaml file. 1. ditz init 2. ditz add-release # add an issue 3. ditz add # where am i? 4. ditz status 5. ditz todo # do work 6. write code 7. ditz close <issue-id> 8. commit 9. goto 3 # finished! 10. ditz release <release-name>"
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.7.0
34
+ version:
35
+ description: "Ditz is a simple, light-weight distributed issue tracker designed to work with distributed version control systems like git, darcs, Mercurial, and Bazaar. It can also be used with centralized systems like SVN. Ditz maintains an issue database directory on disk, with files written in a line-based and human-editable format. This directory can be kept under version control, alongside project code. There are different ways to use ditz: 1. Treat issue change the same as code change: include it as part of commits, and merge it with changes from other developers. (Resolving conflicts in the usual manner.) 2. Keep the issue database in the repository but in a separate branch. Issue changes can be managed by your VCS, but is not tied directly to commits. 3. Keep the issue database separate and not under VCS at all. Your particular usage will depend on what you want to get out of ditz. Ditz provides a simple, console-based interface for creating and updating the issue database file, and some rudimentary HTML generation capabilities for producing world-readable status pages. It currently offers no central public method of bug submission. Synopsis: # set up project. creates the bugs.yaml file. 1. ditz init 2. ditz add-release"
25
36
  email: wmorgan-ditz@masanjin.net
26
37
  executables:
27
38
  - ditz
28
- - ditz-convert-from-monolith
29
39
  extensions: []
30
40
 
31
41
  extra_rdoc_files:
@@ -36,9 +46,9 @@ files:
36
46
  - Rakefile
37
47
  - ReleaseNotes
38
48
  - bin/ditz
39
- - bin/ditz-convert-from-monolith
40
49
  - lib/component.rhtml
41
50
  - lib/ditz.rb
51
+ - lib/hook.rb
42
52
  - lib/html.rb
43
53
  - lib/index.rhtml
44
54
  - lib/issue.rhtml
@@ -51,7 +61,11 @@ files:
51
61
  - lib/style.css
52
62
  - lib/unassigned.rhtml
53
63
  - lib/util.rb
54
- - lib/hook.rb
64
+ - lib/view.rb
65
+ - lib/views.rb
66
+ - lib/plugins/git.rb
67
+ - contrib/completion/ditz.bash
68
+ - contrib/completion/_ditz.zsh
55
69
  has_rdoc: true
56
70
  homepage: http://ditz.rubyforge.org
57
71
  post_install_message:
@@ -75,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
89
  requirements: []
76
90
 
77
91
  rubyforge_project: ditz
78
- rubygems_version: 1.1.1
92
+ rubygems_version: 1.2.0
79
93
  signing_key:
80
94
  specification_version: 2
81
95
  summary: A simple issue tracker designed to integrate well with distributed version control systems like git and darcs. State is saved to a YAML file kept under version control, allowing issues to be closed/added/modified as part of a commit.
@@ -1,42 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'rubygems'
4
- require 'fileutils'
5
- require "ditz"
6
-
7
- PROJECT_FN = "project.yaml"
8
- CONFIG_FN = ".ditz-config"
9
- def ISSUE_TO_FN i; "issue-#{i.id}.yaml" end
10
-
11
- dir = "bugs"
12
- project = Ditz::Project.from "bugs.yaml"
13
- puts "making #{dir}"
14
- FileUtils.mkdir dir
15
- project.changed!
16
- project.issues.each { |i| i.changed! }
17
-
18
- project.validate!
19
- project.assign_issue_names!
20
- project.each_modelobject { |o| o.after_deserialize project }
21
-
22
- ## save project.yaml
23
- dirty = project.each_modelobject { |o| break true if o.changed? } || false
24
- if dirty
25
- fn = File.join dir, PROJECT_FN
26
- puts "writing #{fn}"
27
- project.each_modelobject { |o| o.before_serialize project }
28
- project.save! fn
29
- end
30
-
31
- ## project issues are not model fields proper, so they must be
32
- ## saved independently.
33
- project.issues.each do |i|
34
- if i.changed?
35
- i.before_serialize project
36
- fn = File.join dir, ISSUE_TO_FN(i)
37
- puts "writing #{fn}"
38
- i.save! fn
39
- end
40
- end
41
-
42
- puts "You can delete bugs.yaml now."