madrox 0.1.1 → 0.2.0

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.
data/README.md CHANGED
@@ -3,6 +3,8 @@
3
3
  Distributed Twitter implementation built on Git using content-less commits as
4
4
  tweets.
5
5
 
6
+ ## USAGE
7
+
6
8
  Madrox needs an existing repository with at a single initial commit. "Tweets"
7
9
  are simply commits in a user-specific branch. Use git to push/pull these
8
10
  branches with remote git repositories. Create custom timelines by merging
@@ -26,6 +28,10 @@ them locally.
26
28
  @rick: @bob: nada
27
29
  178f84a58ab1878d1a995ba9ce16ad11e5e58808
28
30
 
31
+ Now, we have three tweets from two different users in their own branch. You
32
+ can merge these two branches into a single branch to see their commits in one
33
+ stream:
34
+
29
35
  $ git branch
30
36
  bob
31
37
  * master
@@ -45,12 +51,79 @@ them locally.
45
51
  @bob: @rick: sup?
46
52
  @rick: Hi
47
53
 
54
+ ## Importing
55
+
56
+ You can import tweets with the `--import` option. You're still bound by Twitter's 3200 tweet limit, unfortunately.
57
+
58
+ $ madrox --import=twitter --since-id=123 --max-id-456 --email=EMAIL TWITTER_LOGIN
59
+
60
+ You can also use `rake console` and import the data yourself from other sources.
61
+
62
+ $ madrox --irb --email=EMAIL TWITTER_LOGIN
63
+ >> tweets.each do |tweet|
64
+ ?> timeline.post(tweet['text'], :committed_date => Time.parse(tweet['created_at']))
65
+ ?> end
66
+
67
+ ## Ruby API
68
+
69
+ The Madrox ruby API revolves around two objects: `Madrox::Repo` and
70
+ `Madrox::Timeline`.
71
+
72
+ `Madrox::Repo` simply tracks the Git repo. It's used to create Timeline
73
+ instances.
74
+
75
+ repo = Madrox::Repo.new "/path/to/repo"
76
+ timeline = repo.timeline('rick', 'rick@email.com')
77
+
78
+ `Madrox::Timeline` represents a branch of the Git repo, and lets you post
79
+ new messages to it. These branches can either represent a user's timeline,
80
+ a grouped timeline with commits merged from multiple users, or something
81
+ custom (such as a user's favorites).
82
+
83
+ timeline.post("Eating a sandwich.")
84
+
85
+ You can list messages from a timeline. They come out as `Grit::Commit`
86
+ instances.
87
+
88
+ mine = repo.timeline('me', 'my-email@email.com')
89
+ rick = repo.timeline('rick')
90
+ msg = rick.messages.first
91
+ msg.sha # => 21f1ca7995b46a1008c402c92c4aa074806f92c4
92
+ msg.message # => "Eating a sandwich."
93
+ msg.committer # => #<Grit::Actor "rick ...">
94
+ msg.committed_date # => Sat Nov 6 11:48:02 -0700 2010
95
+
96
+ You can add a message as a favorite:
97
+
98
+ sha = mine.fave(msg)
99
+ commit = mine.grit.commit(sha)
100
+ commit.sha # => b1dfaf30dff279b953abc8b985bb41e247a0e50c
101
+ commit.message # => "Eating a sandwich."
102
+ commit.committer # => #<Grit::Actor "me ...">
103
+ commit.committed_date # => Sat Nov 6 12:48:02 -0700 2010
104
+ commit.author # => #<Grit::Actor "rick ...">
105
+ commit.authored_date # => Sat Nov 6 11:48:02 -0700 2010
106
+
107
+ You can also retweet the message:
108
+
109
+ sha = mine.retweet(msg)
110
+ commit = mine.grit.commit(sha)
111
+ commit.sha # => d1d22036741d0726901b8e555801885018e7c8df
112
+ commit.message # => "Eating a sandwich."
113
+ commit.committer # => #<Grit::Actor "me ...">
114
+ commit.committed_date # => Sat Nov 6 12:48:02 -0700 2010
115
+ commit.author # => #<Grit::Actor "rick ...">
116
+ commit.authored_date # => Sat Nov 6 11:48:02 -0700 2010
117
+
118
+ Add your own snarky comment:
119
+
120
+ sha = mine.retweet(msg, "TMI, bro!")
121
+ commit = mine.grit.commit(sha)
122
+ commit.sha # => 06836ee40595bf06fde3eb276a08b10ac7733a74
123
+ commit.message # => "TMI, bro! RT @rick Eating a sandwich."
124
+
48
125
  ## TODO
49
126
 
50
127
  * Twitter pushing support
51
128
  * Git Notes for Twitter (or other) metadata.
52
- * Better importing.
53
-
54
- ## Requirements
55
-
56
- gem install grit
129
+ * Better importing.
@@ -1,5 +1,5 @@
1
1
  module Madrox
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
4
4
 
5
5
  $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'vendor', 'grit', 'lib')
@@ -21,12 +21,12 @@ module Madrox
21
21
  # Gets the Madrox object for this timeline.
22
22
  #
23
23
  # Returns a Madrox::Repo instance.
24
- attr_reader :repo
24
+ attr_reader :repo
25
25
 
26
26
  # Gets the Grit object for this Madrox::Repo.
27
27
  #
28
28
  # Returns a Grit::Repo instance.
29
- attr_reader :grit
29
+ attr_reader :grit
30
30
 
31
31
  def initialize(repo, user, email = nil)
32
32
  @user = user
@@ -38,13 +38,27 @@ module Madrox
38
38
  # Public: Gets the messages for this timeline. Automatically removes any
39
39
  # merge commits.
40
40
  #
41
+ # options - Hash of options to filter the message output.
42
+ # :max_count - Fixnum specifying the number of commits to show.
43
+ # Default: 30.
44
+ # :skip - Fixnum specifying the number of commits to skip.
45
+ # :page - Fixnum of the current page. This is used to
46
+ # implicitly calculate the :skip option.
47
+ # Default: 1
48
+ #
41
49
  # Returns an Array of Grit::Commit instances.
42
- def messages
43
- @grit.log(@user).delete_if { |commit| commit.parents.size != 1 }
50
+ def messages(options = {})
51
+ options[:no_merges] = true
52
+ options[:max_count] ||= 30
53
+ options[:skip] ||= begin
54
+ options[:max_count] * ([options.delete(:page).to_i, 1].max - 1)
55
+ end
56
+ @grit.log(@user, nil, options).
57
+ delete_if { |commit| commit.parents.size != 1 }
44
58
  end
45
59
 
46
- # Posts the given message to the timeline. This is a simple commit with
47
- # no changed content. Just a message.
60
+ # Public: Posts the given message to the timeline. This is a simple
61
+ # commit with no changed content. Just a message.
48
62
  #
49
63
  # message - String message for the timeline update.
50
64
  # options - Hash of options passed to Grit::Index#commit.
@@ -52,14 +66,14 @@ module Madrox
52
66
  # Returns a String SHA1 of the created Git commit.
53
67
  def post(message, options = {})
54
68
  idx = @grit.index
55
- parents = [@grit.commit(@user) || @grit.commit("HEAD")]
56
- parents.compact!
57
- options.update(:parents => parents, :committer => actor, :head => @user)
69
+ options = {:committer => actor, :head => @user}.update(options)
70
+ options[:parents] ||= [@grit.commit(@user) || @grit.commit("HEAD")]
71
+ options[:parents].compact!
58
72
  @grit.index.commit(message, options)
59
73
  end
60
74
 
61
- # Retweets a given commit. The author name and date is taken from the
62
- # commit. The message can optionally be annotated.
75
+ # Public: Retweets a given commit. The author name and date is taken
76
+ # from the commit. The message can optionally be annotated.
63
77
  #
64
78
  # commit - The Grit::Commit that is being retweeted.
65
79
  # message - An optional String annotation to the retweet content.
@@ -80,6 +94,15 @@ module Madrox
80
94
  :authored_date => commit.authored_date))
81
95
  end
82
96
 
97
+ # Public: Marks a given commit as a favorite. The commit is stored in a
98
+ # separate branch named "#{user}-favorites". The commit's original
99
+ # committed author and date remain the same, and the new commit tracks
100
+ # the date it was favorited.
101
+ def fave(commit)
102
+ post(commit.message, :head => "#{@user}-favorites",
103
+ :author => commit.author, :authored_date => commit.authored_date)
104
+ end
105
+
83
106
  # Public: Builds a Git actor object for any posted updates to this
84
107
  # timeline. Uses the timelines user and email.
85
108
  #
@@ -13,8 +13,8 @@ Gem::Specification.new do |s|
13
13
  ## If your rubyforge_project name is different, then edit it and comment out
14
14
  ## the sub! line in the Rakefile
15
15
  s.name = 'madrox'
16
- s.version = '0.1.1'
17
- s.date = '2010-10-23'
16
+ s.version = '0.2.0'
17
+ s.date = '2010-11-06'
18
18
  s.rubyforge_project = 'madrox'
19
19
 
20
20
  ## Make sure your summary is short. The description may be as long
@@ -12,15 +12,31 @@ class TimelineTest < MadroxTest
12
12
 
13
13
  def test_finds_messages
14
14
  messages = @repo.timeline('merged').messages
15
- assert_equal 3, messages.size
16
15
  assert_equal %w(
17
16
  f524dcedce5d4f20f3a1d2ecb79f63c8d175d85f
18
17
  e28333e7c6f42004d7d619a1b485072e6361da94
19
18
  cd45ac1b461450245fc104aea0506da6fab1db72), messages.map { |m| m.sha }
20
19
  end
21
20
 
21
+ def test_finds_paginated_messages
22
+ messages = @repo.timeline('merged').messages :page => 1, :max_count => 1
23
+ assert_equal %w(
24
+ f524dcedce5d4f20f3a1d2ecb79f63c8d175d85f), messages.map { |m| m.sha }
25
+
26
+ messages = @repo.timeline('merged').messages :page => 3, :max_count => 1
27
+ assert_equal %w(
28
+ cd45ac1b461450245fc104aea0506da6fab1db72), messages.map { |m| m.sha }
29
+ end
30
+
31
+ def test_skips_messages
32
+ messages = @repo.timeline('merged').messages :skip => 1
33
+ assert_equal %w(
34
+ e28333e7c6f42004d7d619a1b485072e6361da94
35
+ cd45ac1b461450245fc104aea0506da6fab1db72), messages.map { |m| m.sha }
36
+ end
37
+
22
38
  def test_posts_commit
23
- @repo = fork_git_fixture(:simple)
39
+ @repo = fork_git_fixture(:simple)
24
40
  timeline = @repo.timeline('user2', 'user2@email.com')
25
41
  assert_equal %w(e28333e7c6f42004d7d619a1b485072e6361da94),
26
42
  timeline.messages.map { |m| m.sha }
@@ -32,7 +48,7 @@ class TimelineTest < MadroxTest
32
48
  def test_posts_commit_from_date
33
49
  @repo = fork_git_fixture(:simple)
34
50
  timeline = @repo.timeline('user2', 'user2@email.com')
35
- sha = timeline.post('hi', :committed_date => Time.utc(2000))
51
+ sha = timeline.post('hi', :committed_date => Time.utc(2000, 2))
36
52
  commit = @repo.grit.commit(sha)
37
53
  assert_equal 2000, commit.committed_date.year
38
54
  end
@@ -42,7 +58,7 @@ class TimelineTest < MadroxTest
42
58
  timeline1 = @repo.timeline('user1')
43
59
  timeline2 = @repo.timeline('user2')
44
60
  original = timeline1.messages.last
45
- sha = timeline2.retweet(original, :committed_date => Time.utc(2000))
61
+ sha = timeline2.retweet(original, :committed_date => Time.utc(2000, 2))
46
62
  retweet = @repo.grit.commit(sha)
47
63
  assert_equal 1287750442, retweet.authored_date.to_i
48
64
  assert_equal 2000, retweet.committed_date.year
@@ -52,11 +68,11 @@ class TimelineTest < MadroxTest
52
68
  end
53
69
 
54
70
  def test_retweets_with_annotation
55
- @repo = fork_git_fixture(:simple)
71
+ @repo = fork_git_fixture(:simple)
56
72
  timeline1 = @repo.timeline('user1')
57
73
  timeline2 = @repo.timeline('user2')
58
74
  original = timeline1.messages.last
59
- sha = timeline2.retweet(original, 'sweet!', :committed_date => Time.utc(2000))
75
+ sha = timeline2.retweet(original, 'sweet!', :committed_date => Time.utc(2000, 2))
60
76
  retweet = @repo.grit.commit(sha)
61
77
  assert_equal 1287750442, retweet.authored_date.to_i
62
78
  assert_equal 2000, retweet.committed_date.year
@@ -64,4 +80,19 @@ class TimelineTest < MadroxTest
64
80
  assert_equal 'user2', retweet.committer.name
65
81
  assert_equal "sweet! RT @user1 #{original.message}", retweet.message
66
82
  end
83
+
84
+ def test_favoriting_a_tweet
85
+ @repo = fork_git_fixture(:simple)
86
+ timeline1 = @repo.timeline('user1')
87
+ timeline2 = @repo.timeline('user2')
88
+ original = timeline2.messages.last
89
+
90
+ assert_nil @repo.grit.commit('user1-favorites')
91
+ sha = timeline1.fave(original)
92
+ fave = @repo.grit.commit(sha)
93
+ assert_equal 'user1', fave.committer.name
94
+ assert_equal 'user2', fave.author.name
95
+
96
+ assert_equal sha, @repo.grit.commit('user1-favorites').sha
97
+ end
67
98
  end
@@ -4,6 +4,7 @@
4
4
  * Add `git cat-file --batch` support with Grit::Repo#batch.
5
5
  * Minor Enhancements
6
6
  * Grit::Index#commit supports custom committer/author names and dates.
7
+ * Performance enhancements with internal command output buffering.
7
8
  * Bug Fixes
8
9
  * Zero-Padding issue in Grit::Index was fixed.
9
10
 
@@ -40,7 +40,7 @@ module Grit
40
40
  end
41
41
  hours = (time.utc_offset.to_f / 3600).to_i # 60 * 60, seconds to hours
42
42
  rem = time.utc_offset.abs % 3600
43
- out << " #{time.to_i} #{hours >= 0 ? :+ : :-}#{hours.to_s.rjust(2, '0')}#{rem.to_s.rjust(2, '0')}"
43
+ out << " #{time.to_i} #{hours >= 0 ? :+ : :-}#{hours.abs.to_s.rjust(2, '0')}#{rem.to_s.rjust(2, '0')}"
44
44
  end
45
45
 
46
46
  # Pretty object inspection
@@ -317,7 +317,7 @@ module Grit
317
317
  if indata.size == 0
318
318
  raise PackFormatError, 'error reading pack data'
319
319
  end
320
- outdata += zstr.inflate(indata)
320
+ outdata << zstr.inflate(indata)
321
321
  end
322
322
  if outdata.size > destsize
323
323
  raise PackFormatError, 'error reading pack data'
@@ -351,9 +351,9 @@ module Grit
351
351
  cp_size |= delta.getord(pos += 1) << 16 if c & 0x40 != 0
352
352
  cp_size = 0x10000 if cp_size == 0
353
353
  pos += 1
354
- dest += base[cp_off,cp_size]
354
+ dest << base[cp_off,cp_size]
355
355
  elsif c != 0
356
- dest += delta[pos,c]
356
+ dest << delta[pos,c]
357
357
  pos += c
358
358
  else
359
359
  raise PackFormatError, 'invalid delta data'
@@ -255,40 +255,35 @@ module Grit
255
255
 
256
256
  def sh(command, &block)
257
257
  ret, err = '', ''
258
+ max = self.class.git_max_size
258
259
  Open3.popen3(command) do |stdin, stdout, stderr|
259
260
  block.call(stdin) if block
260
261
  Timeout.timeout(self.class.git_timeout) do
261
- while tmp = stdout.read(1024)
262
- ret += tmp
263
- if (@bytes_read += tmp.size) > self.class.git_max_size
264
- bytes = @bytes_read
265
- @bytes_read = 0
266
- raise GitTimeout.new(command, bytes)
267
- end
262
+ while tmp = stdout.read(8192)
263
+ ret << tmp
264
+ raise GitTimeout.new(command, ret.size) if ret.size > max
268
265
  end
269
266
  end
270
267
 
271
- while tmp = stderr.read(1024)
272
- err += tmp
268
+ while tmp = stderr.read(8192)
269
+ err << tmp
273
270
  end
274
271
  end
275
272
  [ret, err]
276
273
  rescue Timeout::Error, Grit::Git::GitTimeout
277
- bytes = @bytes_read
278
- @bytes_read = 0
279
- raise GitTimeout.new(command, bytes)
274
+ raise GitTimeout.new(command, ret.size)
280
275
  end
281
276
 
282
277
  def wild_sh(command, &block)
283
278
  ret, err = '', ''
284
279
  Open3.popen3(command) do |stdin, stdout, stderr|
285
280
  block.call(stdin) if block
286
- while tmp = stdout.read(1024)
287
- ret += tmp
281
+ while tmp = stdout.read(8192)
282
+ ret << tmp
288
283
  end
289
284
 
290
- while tmp = stderr.read(1024)
291
- err += tmp
285
+ while tmp = stderr.read(8192)
286
+ err << tmp
292
287
  end
293
288
  end
294
289
  [ret, err]
@@ -98,15 +98,19 @@ module Grit
98
98
  # Returns a String of the SHA1 of the new commit.
99
99
  def commit(message, parents = nil, actor = nil, last_tree = nil, head = 'master')
100
100
  if parents.is_a?(Hash)
101
- committer = parents[:committer] || parents[:actor]
101
+ actor = parents[:actor]
102
+ committer = parents[:committer]
102
103
  author = parents[:author]
103
104
  last_tree = parents[:last_tree]
104
105
  head = parents[:head]
105
106
  committed_date = parents[:committed_date]
106
107
  authored_date = parents[:authored_date]
107
- parents = parents[:parents] || []
108
+ parents = parents[:parents]
108
109
  end
109
110
 
111
+ committer ||= actor
112
+ author ||= committer
113
+
110
114
  tree_sha1 = write_tree(self.tree, self.current_tree)
111
115
 
112
116
  # don't write identical commits
@@ -5,6 +5,14 @@ class TestActor < Test::Unit::TestCase
5
5
 
6
6
  end
7
7
 
8
+ # output
9
+ def test_output_adds_tz_offset
10
+ t = Time.now
11
+ a = Actor.new("Tom Werner", "tom@example.com")
12
+ assert_equal "Tom Werner <tom@example.com> #{t.to_i} -0700",
13
+ a.output(t)
14
+ end
15
+
8
16
  # from_string
9
17
 
10
18
  def test_from_string_should_separate_name_and_email
@@ -49,9 +49,8 @@ class TestGit < Test::Unit::TestCase
49
49
  end
50
50
 
51
51
  def test_raises_if_too_many_bytes
52
- @git.instance_variable_set(:@bytes_read, 6000000)
53
52
  assert_raises Grit::Git::GitTimeout do
54
- @git.version
53
+ @git.sh "yes | head -#{Grit::Git.git_max_size + 1}"
55
54
  end
56
55
  end
57
56
 
@@ -32,11 +32,20 @@ class TestRubyGitIndex < Test::Unit::TestCase
32
32
  assert_equal now.day, commit.committed_date.day
33
33
  end
34
34
 
35
+ def test_set_actor
36
+ parents = [@git.commits.first]
37
+ sha = @git.index.commit('message', parents, @user)
38
+
39
+ commit = @git.commit(sha)
40
+ assert_equal @user.name, commit.committer.name
41
+ assert_equal @user.name, commit.author.name
42
+ end
43
+
35
44
  def test_allow_custom_committed_and_authored_dates
36
45
  parents = [@git.commits.first]
37
46
  sha = @git.index.commit 'message',
38
- :committed_date => Time.utc(2000),
39
- :authored_date => Time.utc(2001),
47
+ :committed_date => Time.local(2000),
48
+ :authored_date => Time.local(2001),
40
49
  :parents => parents,
41
50
  :actor => @user,
42
51
  :head => 'master'
@@ -52,10 +61,11 @@ class TestRubyGitIndex < Test::Unit::TestCase
52
61
  sha = @git.index.commit 'message',
53
62
  :committer => Grit::Actor.new('abc', nil),
54
63
  :author => Grit::Actor.new('def', nil),
55
- :parents => parents,
56
- :head => 'master'
64
+ :parents => parents,
65
+ :head => 'master'
57
66
 
58
67
  commit = @git.commit(sha)
68
+ assert_equal parents.map { |c| c.sha }, commit.parents.map { |c| c.sha }
59
69
  assert_equal 'abc', commit.committer.name
60
70
  assert_equal 'def', commit.author.name
61
71
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
8
- - 1
9
- version: 0.1.1
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Rick Olson
@@ -14,14 +14,13 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-10-23 00:00:00 +02:00
17
+ date: 2010-11-06 00:00:00 -07:00
18
18
  default_executable: madrox
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: yajl-ruby
22
22
  prerelease: false
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
- none: false
25
24
  requirements:
26
25
  - - ~>
27
26
  - !ruby/object:Gem::Version
@@ -1505,7 +1504,6 @@ rdoc_options: []
1505
1504
  require_paths:
1506
1505
  - lib
1507
1506
  required_ruby_version: !ruby/object:Gem::Requirement
1508
- none: false
1509
1507
  requirements:
1510
1508
  - - ">="
1511
1509
  - !ruby/object:Gem::Version
@@ -1513,7 +1511,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
1513
1511
  - 0
1514
1512
  version: "0"
1515
1513
  required_rubygems_version: !ruby/object:Gem::Requirement
1516
- none: false
1517
1514
  requirements:
1518
1515
  - - ">="
1519
1516
  - !ruby/object:Gem::Version
@@ -1523,7 +1520,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1523
1520
  requirements: []
1524
1521
 
1525
1522
  rubyforge_project: madrox
1526
- rubygems_version: 1.3.7
1523
+ rubygems_version: 1.3.6
1527
1524
  signing_key:
1528
1525
  specification_version: 2
1529
1526
  summary: Distributed Twitter implementation on Git.