madrox 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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.