twit 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 289a3be1ed0e67bea59cffdce6e82f2802511b2c
4
- data.tar.gz: 7690c43ff2be9ab8f81a26757f754162b4f57818
3
+ metadata.gz: 85fc88fe59a3282afb1f4e29af583408a35a3f29
4
+ data.tar.gz: a30902725f73eeeb3ca2b84666ef23908292e265
5
5
  SHA512:
6
- metadata.gz: bcbd3f91dd0a59ea2c4162b0449ea8b999e2d45e05105cc11c9fd6a71ddba3775da26ff7f901805d1f1af8a7a614e4f8e680c643476dfd1302807982c102bf0b
7
- data.tar.gz: 7a2097cad0b49b9c9a312433dfe344b5ad4ce5f2c29d18e53a31cc41adaa6c4493417565cbf890a62575d6a40f74c468123db3b99d01d35d55d55e3d59371112
6
+ metadata.gz: e82495bbc0018af33c6f33ae5ea9e14ebedd0a35c95ee6435fd6a0d0c227162dd76ebf93e70ab3f76687c481fd91d9979edf8024e50121da5c9da098b1d41812
7
+ data.tar.gz: 4a295fe77e361dd9769a72fe10494c50d9be7dcc4c6a854a5c2bfbf944ab59a6075e8d9e1e5229eb22ee8b33efab87a5e7e6c38034b1f7b4fdcd670b89a4ab51
data/.gitignore CHANGED
@@ -16,3 +16,6 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  bin
19
+ *~
20
+ .*.swp
21
+ Session.vim
@@ -1,6 +1,39 @@
1
- # Twit: simplified git wrapper
1
+ # Twit: training wheels for Git
2
2
 
3
- Twit makes [git](http://git-scm.com) easier for beginners.
3
+ Twit is a wrapper for [Git](http://git-scm.com) that abstracts concepts that
4
+ many newcomers find tedious or confusing.
5
+
6
+ When explaining version control to newcomers, the benefits are often unclear
7
+ amid the complicated rules and syntax of a powerful tool like Git. Twit aims to
8
+ provide an *easy and functional* demonstration of the usefulness of a branching
9
+ version control system.
10
+
11
+ For example, you can say that "version control allows you to save snapshots of
12
+ your project history." However, in order to do this, you need to understand
13
+ Git's two-step stage-then-commit workflow, with all its corner cases regarding
14
+ new/deleted/moved files.
15
+
16
+ Instead, Twit exposes a simple command to create a new commit with a snapshot
17
+ of the repository:
18
+
19
+ twit save
20
+
21
+ This stages any changes (including new files and deletions) and creates a
22
+ commit, prompting the user for a commit message if needed.
23
+
24
+ To create a new branch (and save any changes to the new branch as well):
25
+
26
+ twit saveas my_new_branch
27
+
28
+ This quick-and-easy approach allows a new user to get started using version
29
+ control right away, without having to learn Git's minutiae.
30
+
31
+ However, this simple program is **not** meant to replace Git for the power
32
+ user. The interface was designed to be user-friendly at the cost of
33
+ flexibility. If you are a programmer, you should probably just buckle down and
34
+ [learn git](http://gitref.org). Instead, Twit serves as an introduction to
35
+ version control for users that would probably never learn Git, like writers or
36
+ students.
4
37
 
5
38
  ## Installation
6
39
 
@@ -18,14 +51,53 @@ To install, simply run:
18
51
 
19
52
  Initialize a new git repository in the current directory.
20
53
 
21
- ### `save` -- take a snapshot of all files
54
+ Equivalent to: `git init`
55
+
56
+ ### `save` -- commit all new changes to the current branch
22
57
 
23
- twit save <DESCRIBE_CHANGES>
58
+ twit save [DESCRIBE_CHANGES]
24
59
 
25
60
  Take a snapshot of all files in the directory.
26
61
 
27
62
  Any changes on the working tree will be committed to the current branch.
28
63
 
64
+ Equivalent to: `git add --all && git commit -m <DESCRIBE_CHANGES>`
65
+
66
+ ### `saveas` -- commit all new changes to a new branch
67
+
68
+ twit saveas [NEW_BRANCH] [DESCRIBE_CHANGES]
69
+
70
+ Equivalent to: `git checkout -b <NEW_BRANCH>` then `twit save`
71
+
72
+ ### `open` -- open another branch
73
+
74
+ twit open [BRANCH]
75
+
76
+ Equivalent to: `git checkout <branch>`
77
+
78
+ ### `include` -- incorperate changes from another branch
79
+
80
+ twit include [OTHER_BRANCH]
81
+
82
+ Incorperate changes from another branch, but do not save them yet. (The user
83
+ can resolve any conflicts and then run `twit save` themselves.)
84
+
85
+ Equivalent to: `git merge --no-ff --no-commit [OTHER_BRANCH]`
86
+
87
+ ### `discard` -- permanently delete unsaved changes
88
+
89
+ twit discard
90
+
91
+ **Permanently** delete any unsaved changes to the current branch. Be careful!
92
+
93
+ Equivalent to: `git reset --hard`
94
+
95
+ ### `list` -- show all branches
96
+
97
+ twit list
98
+
99
+ Equivalent to: `git branch`
100
+
29
101
  ## API
30
102
 
31
103
  All command-line functions are available for use as a Ruby library as well.
@@ -41,6 +113,9 @@ All command-line functions are available for use as a Ruby library as well.
41
113
  # Take a snapshot
42
114
  Twit.save "Add some foo"
43
115
 
116
+ # Create a new branch
117
+ Twit.saveas "feature-branch"
118
+
44
119
  ## Development
45
120
 
46
121
  ### Setup
@@ -1,8 +1,9 @@
1
1
  require "twit/version"
2
2
  require "twit/repo"
3
- require "twit/init"
4
3
  require "twit/error"
5
4
 
5
+ require 'open3'
6
+
6
7
  # This module exposes twit commands as methods.
7
8
  module Twit
8
9
 
@@ -12,4 +13,90 @@ module Twit
12
13
  Twit::Repo.new
13
14
  end
14
15
 
16
+ # Initialize a git repository in a directory. Return a Twit::Repo object
17
+ # representing the new repository.
18
+ #
19
+ # If no argument is supplied, use the working directory.
20
+ #
21
+ # If init is called on a directory that is already part of a repository,
22
+ # simply do nothing.
23
+ def self.init dir = nil
24
+ dir ||= Dir.getwd
25
+
26
+ if is_repo? dir
27
+ return
28
+ end
29
+
30
+ Dir.chdir dir do
31
+ stdout, stderr, status = Open3.capture3 "git init"
32
+ if status != 0
33
+ raise Error, stderr
34
+ end
35
+ end
36
+ Repo.new dir
37
+ end
38
+
39
+ # Check if a given directory is a git repository.
40
+ #
41
+ # If no argument is supplied, use the working directory.
42
+ def self.is_repo? dir = nil
43
+ dir ||= Dir.getwd
44
+ Dir.chdir dir do
45
+ stdout, stderr, status = Open3.capture3 "git status"
46
+ if status != 0
47
+ if /Not a git repository/.match stderr
48
+ return false
49
+ else
50
+ raise Error, stderr
51
+ end
52
+ end
53
+ return true
54
+ end
55
+ end
56
+
57
+ # See {Twit::Repo#save}.
58
+ def self.save message
59
+ self.repo.save message
60
+ end
61
+
62
+ # See {Twit::Repo#saveas}.
63
+ def self.saveas branch, message = nil
64
+ self.repo.saveas branch, message
65
+ end
66
+
67
+ # See {Twit::Repo#discard}. (WARNING: PERMANENTLY DESTROYS DATA!)
68
+ def self.discard
69
+ self.repo.discard
70
+ end
71
+
72
+ # See {Twit::Repo#open}.
73
+ def self.open branch
74
+ self.repo.open branch
75
+ end
76
+
77
+ # See {Twit::Repo#include}.
78
+ def self.include branch
79
+ self.repo.include branch
80
+ end
81
+
82
+ # See {Twit::Repo#include_into}.
83
+ def self.include_into branch
84
+ self.repo.include_into branch
85
+ end
86
+
87
+ # See {Twit::Repo#list}.
88
+ def self.list
89
+ self.repo.list
90
+ end
91
+
92
+ # See {Twit::Repo#current_branch}.
93
+ def self.current_branch
94
+ self.repo.current_branch
95
+ end
96
+
97
+ # See {Twit::Repo#nothing_to_commit?}.
98
+ def self.nothing_to_commit?
99
+ self.repo.nothing_to_commit?
100
+ end
101
+
15
102
  end
@@ -5,24 +5,143 @@ module Twit
5
5
 
6
6
  # Automatically-built command-line interface (using Thor).
7
7
  class CLI < Thor
8
+ include Thor::Actions
8
9
 
9
10
  desc "init", "Create an empty repository in the current directory"
10
- # Pass method to Twit module
11
+ # See {Twit::init}
11
12
  def init
12
- Twit.init
13
+ begin
14
+ if Twit.is_repo?
15
+ say "You are already in a repository! (Root directory: #{Twit.repo.root})"
16
+ return
17
+ end
18
+ Twit.init
19
+ rescue Error => e
20
+ say "Error: #{e.message}"
21
+ end
13
22
  end
14
23
 
15
- desc "save <DESCRIBE_CHANGES>", "Take a snapshot of all files"
16
- # Pass method to default repo
24
+ desc "save [DESCRIBE_CHANGES]", "Take a snapshot of all files"
25
+ # See {Twit::Repo#save}.
17
26
  def save message = nil
18
- if message.nil?
19
- $stderr.puts "Please supply a message describing your changes.\n e.g. twit save \"Update the README\""
20
- exit false
27
+ begin
28
+ if Twit.nothing_to_commit?
29
+ say "No new edits to save"
30
+ return
31
+ end
32
+ while message.nil? || message.strip == ""
33
+ message = ask "Please supply a message describing your changes:"
34
+ end
35
+ Twit.save message
36
+ rescue Error => e
37
+ say "Error: #{e.message}"
38
+ end
39
+ end
40
+
41
+ desc "saveas [NEW_BRANCH] [DESCRIBE_CHANGES]", "Save snapshot to new branch"
42
+ # See {Twit::Repo#saveas}.
43
+ def saveas branch = nil, message = nil
44
+ while branch.nil? || branch.strip == ""
45
+ branch = ask "Please supply the name for your new branch:"
46
+ end
47
+ begin
48
+ if (not Twit.nothing_to_commit?) && message.nil?
49
+ message = ask "Please supply a message describing your changes:"
50
+ end
51
+ begin
52
+ Twit.saveas branch, message
53
+ rescue InvalidParameter => e
54
+ if /already exists/.match e.message
55
+ say "Cannot saveas to existing branch. See \"twit help include_into\""
56
+ else
57
+ say "Error: #{e.message}"
58
+ end
59
+ end
60
+ rescue Error => e
61
+ say "Error: #{e.message}"
62
+ end
63
+ end
64
+
65
+ desc "open [BRANCH]", "Open a branch"
66
+ # See {Twit::Repo#open}.
67
+ def open branch = nil
68
+ while branch.nil? || branch.strip == ""
69
+ branch = ask "Please enter the name of the branch to open:"
70
+ end
71
+ begin
72
+ Twit.open branch
73
+ rescue Error => e
74
+ say "Error: #{e.message}"
75
+ else
76
+ say "Opened #{branch}."
77
+ end
78
+ end
79
+
80
+ desc "list", "Display a list of branches"
81
+ # See {Twit::Repo#list} and {Twit::Repo#current_branch}
82
+ def list
83
+ begin
84
+ @current = Twit.current_branch
85
+ @others = Twit.list.reject { |b| b == @current }
86
+ rescue Error => e
87
+ say "Error: #{e.message}"
88
+ return
89
+ end
90
+ say "Current branch: #{@current}"
91
+ say "Other branches:"
92
+ @others.each do |branch|
93
+ say "- #{branch}"
94
+ end
95
+ end
96
+
97
+ desc "include", "Integrate changes from another branch"
98
+ # See {Twit::Repo#include}.
99
+ def include other_branch = nil
100
+ while other_branch.nil? || other_branch.strip == ""
101
+ other_branch = ask "Please enter the name of the branch to pull changes from:"
102
+ end
103
+ begin
104
+ @success = Twit.include other_branch
105
+ rescue Error => e
106
+ say "Error: #{e.message}"
107
+ return
108
+ end
109
+ if @success
110
+ say "Changes from #{other_branch} successfully included into #{Twit.current_branch}."
111
+ say "Use \"twit save\" to save them."
112
+ else
113
+ say "Conflicts detected! Fix them, then use \"twit save\" to save the changes."
21
114
  end
115
+ end
116
+
117
+ desc "include_into", "Integrate changes into another branch"
118
+ # See {Twit::Repo#include}.
119
+ def include_into other_branch = nil
120
+ while other_branch.nil? || other_branch.strip == ""
121
+ other_branch = ask "Please enter the name of the branch to push changes into:"
122
+ end
123
+ begin
124
+ @original = Twit.current_branch
125
+ @success = Twit.include_into other_branch
126
+ rescue Error => e
127
+ say "Error: #{e.message}"
128
+ return
129
+ end
130
+ if @success
131
+ say "Changes from #{@original} successfully included into #{other_branch}."
132
+ say "Use \"twit save\" to save them."
133
+ else
134
+ say "Conflicts detected! Fix them, then use \"twit save\" to save the changes."
135
+ end
136
+ end
137
+
138
+ desc "discard", "PERMANTENTLY delete all changes since last save"
139
+ # See {Twit::Repo#discard}.
140
+ def discard
22
141
  begin
23
- Twit.repo.save message
24
- rescue NothingToCommitError
25
- puts "No new edits to save"
142
+ Twit.discard
143
+ rescue Error => e
144
+ say "Error: #{e.message}"
26
145
  end
27
146
  end
28
147
 
@@ -10,4 +10,7 @@ module Twit
10
10
  # Raised when trying to commit nothing.
11
11
  class NothingToCommitError < Error; end
12
12
 
13
+ # Raised when a command receives an invalid parameter.
14
+ class InvalidParameter < Error; end
15
+
13
16
  end
@@ -1,6 +1,5 @@
1
1
  require 'open3'
2
2
  require 'twit/error'
3
- require 'twit/repo/save'
4
3
 
5
4
  module Twit
6
5
 
@@ -30,6 +29,177 @@ module Twit
30
29
  end
31
30
  @root = root
32
31
  end
32
+
33
+ # Update the snapshot of the current directory.
34
+ def save message
35
+ Dir.chdir @root do
36
+ cmd = "git add --all && git commit -m \"#{message}\""
37
+ stdout, stderr, status = Open3.capture3 cmd
38
+ if status != 0
39
+ if /nothing to commit/.match stdout
40
+ raise NothingToCommitError
41
+ elsif /Not a git repository/.match stderr
42
+ raise NotARepositoryError
43
+ else
44
+ raise Error, stderr
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ # Save the current state of the repository to a new branch.
51
+ def saveas newbranch, message = nil
52
+ message ||= "Create new branch: #{newbranch}"
53
+ # First, create the new branch and switch to it.
54
+ Dir.chdir @root do
55
+ cmd = "git checkout -b \"#{newbranch}\""
56
+ stdout, stderr, status = Open3.capture3 cmd
57
+ if status != 0
58
+ case stderr
59
+ when /not a valid branch name/
60
+ raise InvalidParameter, "#{newbranch} is not a valid branch name"
61
+ when /already exists/
62
+ raise InvalidParameter, "#{newbranch} already exists"
63
+ when /Not a git repository/
64
+ raise NotARepositoryError
65
+ else
66
+ raise Error, stderr
67
+ end
68
+ end
69
+ end
70
+ # Next, save any working changes.
71
+ begin
72
+ Twit.save message
73
+ rescue NothingToCommitError
74
+ # New changes are not required for saveas.
75
+ end
76
+ end
77
+
78
+ # Return an Array of branches in the repo.
79
+ def list
80
+ Dir.chdir @root do
81
+ cmd = "git branch"
82
+ stdout, stderr, status = Open3.capture3 cmd
83
+ if status != 0
84
+ case stderr
85
+ when /Not a git repository/
86
+ raise NotARepositoryError
87
+ else
88
+ raise Error, stderr
89
+ end
90
+ end
91
+ return stdout.split.map { |s|
92
+ # Remove trailing/leading whitespace and astericks
93
+ s.sub('*', '').strip
94
+ }.reject { |s|
95
+ # Drop elements created due to trailing newline
96
+ s.size == 0
97
+ }
98
+ end
99
+ end
100
+
101
+ # Return the current branch.
102
+ def current_branch
103
+ Dir.chdir @root do
104
+ cmd = "git rev-parse --abbrev-ref HEAD"
105
+ stdout, stderr, status = Open3.capture3 cmd
106
+ if status != 0
107
+ case stderr
108
+ when /unknown revision/
109
+ raise Error, "could not determine branch of repo with no commits"
110
+ when /Not a git repository/
111
+ raise NotARepositoryError
112
+ else
113
+ raise Error, stderr
114
+ end
115
+ end
116
+ return stdout.strip
117
+ end
118
+ end
119
+
120
+ # Open a branch.
121
+ def open branch
122
+ Dir.chdir @root do
123
+ cmd = "git checkout \"#{branch}\""
124
+ stdout, stderr, status = Open3.capture3 cmd
125
+ if status != 0
126
+ case stderr
127
+ when /Not a git repository/
128
+ raise NotARepositoryError
129
+ when /pathspec '#{branch}' did not match any/
130
+ raise InvalidParameter, "#{branch} does not exist"
131
+ else
132
+ raise Error, stderr
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ # Clean the working directory (permanently deletes changes!!!).
139
+ def discard
140
+ Dir.chdir @root do
141
+ # First, add all files to the index. (Otherwise, we won't discard new
142
+ # files.) Then, hard reset to revert to the last saved state.
143
+ cmd = "git add --all && git reset --hard"
144
+ stdout, stderr, status = Open3.capture3 cmd
145
+ if status != 0
146
+ case stderr
147
+ when /Not a git repository/
148
+ raise NotARepositoryError
149
+ else
150
+ raise Error, stderr
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ # Incorperate changes from another branch, but do not commit them.
157
+ #
158
+ # Return true if the merge was successful without conflicts; false if there
159
+ # are conflicts.
160
+ def include other_branch
161
+ Dir.chdir @root do
162
+ cmd = "git merge --no-ff --no-commit \"#{other_branch}\""
163
+ stdout, stderr, status = Open3.capture3 cmd
164
+ if status != 0
165
+ if /Not a git repository/.match stderr
166
+ raise NotARepositoryError
167
+ elsif /Automatic merge failed/.match stdout
168
+ return false
169
+ else
170
+ raise Error, stderr
171
+ end
172
+ end
173
+ end
174
+ return true
175
+ end
176
+
177
+ # Reverse of {Twit::Repo#include} -- incorperate changes from the current
178
+ # branch into another.
179
+ def include_into other_branch
180
+ original_branch = current_branch
181
+ open other_branch
182
+ include(original_branch)
183
+ end
184
+
185
+ # Return true if there is nothing new to commit.
186
+ def nothing_to_commit?
187
+ Dir.chdir @root do
188
+ cmd = "git status"
189
+ stdout, stderr, status = Open3.capture3 cmd
190
+ if status != 0
191
+ case stderr
192
+ when /Not a git repository/
193
+ raise NotARepositoryError
194
+ else
195
+ raise Error, stderr
196
+ end
197
+ end
198
+ # Check if status indicates nothing to commit
199
+ return /nothing to commit/.match stdout
200
+ end
201
+ end
202
+
33
203
  end
34
204
 
35
205
  end
@@ -1,4 +1,4 @@
1
1
  module Twit
2
2
  # Gem version
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.2"
4
4
  end
@@ -0,0 +1,15 @@
1
+ require 'tmpdir'
2
+ require 'twit'
3
+
4
+ shared_context "temp repo" do
5
+ before do
6
+ @tmpdir = Dir.mktmpdir
7
+ @repo = Twit.init @tmpdir
8
+ @oldwd = Dir.getwd
9
+ Dir.chdir @tmpdir
10
+ end
11
+ after do
12
+ Dir.chdir @oldwd
13
+ FileUtils.remove_entry @tmpdir
14
+ end
15
+ end
@@ -11,28 +11,44 @@ describe Twit::CLI do
11
11
  end
12
12
 
13
13
  describe "init" do
14
- it "calls Twit.init" do
15
- Twit.should_receive(:init)
16
- @cli.invoke :init
14
+ context "is not repo" do
15
+ before do
16
+ Twit.stub(:'is_repo?') { false }
17
+ end
18
+ it "calls Twit.init" do
19
+ expect(Twit).to receive(:init)
20
+ @cli.invoke :init
21
+ end
17
22
  end
18
23
  end
19
24
 
20
25
  describe "save" do
21
- it "calls Twit.repo.save" do
22
- message = "my test commit message"
23
- repo = double('repo')
24
-
25
- # Expected call is `Twit.repo.save message`
26
- repo.should_receive(:save).with(message)
27
- Twit.should_receive(:repo).and_return(repo)
26
+ context "need to commit" do
27
+ before do
28
+ Twit.stub(:'nothing_to_commit?') { false }
29
+ end
30
+ it "calls Twit.save" do
31
+ message = "my test commit message"
32
+ expect(Twit).to receive(:save).with(message)
33
+ @cli.invoke :save, [message]
34
+ end
35
+ end
36
+ end
28
37
 
29
- @cli.invoke :save, [message]
38
+ describe "saveas" do
39
+ it "calls Twit.saveas" do
40
+ Twit.stub(:'nothing_to_commit?') { false }
41
+ branch = "my_branch"
42
+ message = "my test commit message"
43
+ expect(Twit).to receive(:saveas).with(branch, message)
44
+ @cli.invoke :saveas, [branch, message]
30
45
  end
31
- it "asks for commit message" do
32
- $stderr.should_receive(:puts).with /message/
33
- expect {
34
- @cli.invoke :save
35
- }.to raise_error SystemExit
46
+ end
47
+
48
+ describe "discard" do
49
+ it "calls Twit.discard" do
50
+ expect(Twit).to receive(:discard)
51
+ @cli.invoke :discard
36
52
  end
37
53
  end
38
54
 
@@ -1,10 +1,12 @@
1
1
  require 'tmpdir'
2
2
  require 'tempfile'
3
+ require 'securerandom'
3
4
 
4
- require 'twit/repo'
5
- require 'twit/error'
5
+ require 'twit'
6
+ require 'spec_helper'
6
7
 
7
8
  describe Twit::Repo do
9
+
8
10
  describe "#new" do
9
11
 
10
12
  before do
@@ -58,4 +60,295 @@ describe Twit::Repo do
58
60
  end
59
61
 
60
62
  end
63
+
64
+ describe "#save" do
65
+
66
+ include_context "temp repo"
67
+
68
+ context "files in working tree" do
69
+ before do
70
+ 3.times do |i|
71
+ File.open("file#{i}.txt", 'w') { |f| f.write("file#{i} contents\n") }
72
+ end
73
+ end
74
+ it "commits the entire working tree" do
75
+ @repo.save "created three files"
76
+ expect(`git status`).to include('working directory clean')
77
+ end
78
+ it "makes a commit" do
79
+ msg = "commit msg #{SecureRandom.hex(4)}"
80
+ @repo.save msg
81
+ expect(`git log`).to include(msg)
82
+ end
83
+ end
84
+
85
+ it "raises error in new repository" do
86
+ expect {
87
+ @repo.save "trying to save"
88
+ }.to raise_error(Twit::NothingToCommitError)
89
+ end
90
+
91
+ it "raises error with nothing to commit" do
92
+ # First, make sure there's at least one commit on the log.
93
+ File.open("foo", 'w') { |f| f.write("bar\n") }
94
+ `git add foo && git commit -m "add foo"`
95
+
96
+ # Now there should be nothing more to commit
97
+ expect {
98
+ @repo.save "trying to save"
99
+ }.to raise_error(Twit::NothingToCommitError)
100
+ end
101
+
102
+ end
103
+
104
+ describe "#list" do
105
+
106
+ include_context "temp repo"
107
+
108
+ it "should return an Array of branches" do
109
+ # Make sure there's at least one commit on master.
110
+ File.open("foo", 'w') { |f| f.write("bar\n") }
111
+
112
+ @repo.save "Initial commit"
113
+
114
+ branches = ['all', 'your', 'branch', 'are',
115
+ 'belong', 'to', 'us']
116
+ branches.each do |branch|
117
+ `git branch #{branch}`
118
+ end
119
+
120
+ expect(@repo.list).to match_array(branches + ['master'])
121
+ end
122
+
123
+ it "should return empty on an empty repo" do
124
+ expect(@repo.list).to match_array([])
125
+ end
126
+
127
+ end
128
+
129
+ describe "#discard" do
130
+
131
+ include_context "temp repo"
132
+
133
+ context "with one commit and dirty working tree" do
134
+
135
+ before do
136
+ # Make sure there's at least one commit on master.
137
+ File.open("foo", 'w') { |f| f.write("bar\n") }
138
+ @repo.save "Initial commit"
139
+
140
+ # Pollute the working tree with spam
141
+ File.open("spam", 'w') { |f| f.write("eggs\n") }
142
+ end
143
+
144
+ it "should clean the working tree" do
145
+ @repo.discard
146
+ expect(`git status`).to include('working directory clean')
147
+ end
148
+
149
+ it "should delete the spam file" do
150
+ @repo.discard
151
+ expect(Pathname "spam").not_to exist
152
+ end
153
+
154
+ end
155
+
156
+ end
157
+
158
+ describe "#saveas" do
159
+
160
+ include_context "temp repo"
161
+
162
+ context "files in working tree" do
163
+ before do
164
+ 3.times do |i|
165
+ File.open("file#{i}.txt", 'w') { |f| f.write("file#{i} contents\n") }
166
+ end
167
+ end
168
+ it "commits the entire working tree" do
169
+ @repo.saveas "newbranch"
170
+ expect(`git status`).to include('working directory clean')
171
+ end
172
+ it "makes a commit" do
173
+ msg = "commit msg #{SecureRandom.hex(4)}"
174
+ @repo.saveas "newbranch", msg
175
+ expect(`git log`).to include(msg)
176
+ end
177
+ it "creates a new branch" do
178
+ new_branch = "branch-#{SecureRandom.hex(4)}"
179
+ @repo.saveas new_branch
180
+ current_branch = `git rev-parse --abbrev-ref HEAD`.strip
181
+ expect(current_branch).to eq(new_branch)
182
+ end
183
+ end
184
+
185
+ it "raises an error with an invalid branch name" do
186
+ expect {
187
+ @repo.saveas "new branch"
188
+ }.to raise_error(Twit::InvalidParameter)
189
+ end
190
+
191
+ it "raises an error when the branch already exists" do
192
+ branch = "my_branch"
193
+ File.open("foo", 'w') { |f| f.write("bar\n") }
194
+ @repo.saveas branch
195
+ expect {
196
+ @repo.saveas branch
197
+ }.to raise_error(Twit::InvalidParameter)
198
+ end
199
+
200
+ it "does not raise error in new repository" do
201
+ expect {
202
+ @repo.saveas "newbranch"
203
+ }.not_to raise_error
204
+ end
205
+
206
+ it "does not raise error with nothing to commit" do
207
+ # First, make sure there's at least one commit on the log.
208
+ File.open("foo", 'w') { |f| f.write("bar\n") }
209
+ `git add foo && git commit -m "add foo"`
210
+
211
+ # Now there should be nothing more to commit, but no error should be
212
+ # raised.
213
+ expect {
214
+ @repo.saveas "newbranch"
215
+ }.not_to raise_error
216
+ end
217
+
218
+ end
219
+
220
+ describe "#current_branch" do
221
+
222
+ include_context "temp repo"
223
+
224
+ it "returns the name of the current branch" do
225
+ new_branch = "branch-#{SecureRandom.hex(4)}"
226
+ File.open("foo", 'w') { |f| f.write("bar\n") }
227
+ @repo.saveas new_branch
228
+ expect(@repo.current_branch).to eq(new_branch)
229
+ end
230
+
231
+ end
232
+
233
+ describe "#nothing_to_commit?" do
234
+ include_context "temp repo"
235
+ it "returns true in fresh repo" do
236
+ expect(@repo.nothing_to_commit?).to be_true
237
+ end
238
+ it "returns false with file in working tree" do
239
+ File.open("foo", 'w') { |f| f.write("bar\n") }
240
+ expect(@repo.nothing_to_commit?).to be_false
241
+ end
242
+ it "returns true after one commit" do
243
+ File.open("foo", 'w') { |f| f.write("bar\n") }
244
+ @repo.save "commit"
245
+ expect(@repo.nothing_to_commit?).to be_true
246
+ end
247
+ end
248
+
249
+ describe "#open" do
250
+
251
+ include_context "temp repo"
252
+
253
+ it "opens a branch" do
254
+ new_branch1 = "branch-#{SecureRandom.hex(4)}"
255
+ File.open("foo", 'w') { |f| f.write("bar\n") }
256
+ @repo.saveas new_branch1
257
+
258
+ new_branch2 = "branch-#{SecureRandom.hex(4)}"
259
+ @repo.saveas new_branch2
260
+
261
+ @repo.open new_branch1
262
+ current_branch = `git rev-parse --abbrev-ref HEAD`.strip
263
+ expect(current_branch).to eq(new_branch1)
264
+ end
265
+
266
+ it "raises error when branch does not exist" do
267
+ expect {
268
+ @repo.open "spam"
269
+ }.to raise_error(Twit::InvalidParameter)
270
+ end
271
+
272
+ end
273
+
274
+ describe "#include" do
275
+
276
+ include_context "temp repo"
277
+
278
+ shared_examples "simple merge" do
279
+ it "will create a merge commit" do
280
+ expect(`git status`).to include('All conflicts fixed but you are still merging')
281
+ end
282
+ end
283
+
284
+ context "fast-forward merge" do
285
+ before do
286
+ # First commit to master
287
+ File.open("foo", 'w') { |f| f.write("bar\n") }
288
+ @repo.save "add foo"
289
+ # Next commit to feature
290
+ File.open("spam", 'w') { |f| f.write("eggs\n") }
291
+ @repo.saveas "feature"
292
+ @repo.open "master"
293
+ @mergestatus = @repo.include "feature"
294
+ end
295
+ include_examples "simple merge"
296
+ end
297
+
298
+ context "no-conflict merge" do
299
+ before do
300
+ # First commit to master
301
+ File.open("foo", 'w') { |f| f.write("bar\n") }
302
+ @repo.save "add foo"
303
+ # Next commit to feature
304
+ File.open("spam", 'w') { |f| f.write("eggs\n") }
305
+ @repo.saveas "feature"
306
+ # Add something else to master
307
+ @repo.open "master"
308
+ File.open("else", 'w') { |f| f.write("continued\n") }
309
+ @repo.save "continue work"
310
+ @mergestatus = @repo.include "feature"
311
+ end
312
+ include_examples "simple merge"
313
+ end
314
+
315
+ context "conflict merge" do
316
+ before do
317
+ # First commit to master
318
+ File.open("foo", 'w') { |f| f.write("bar\n") }
319
+ @repo.save "add foo"
320
+ # Next commit to feature
321
+ File.open("spam", 'w') { |f| f.write("eggs\n") }
322
+ @repo.saveas "feature"
323
+ # Conflicting commit to master
324
+ @repo.open "master"
325
+ File.open("spam", 'w') { |f| f.write("spam\n") }
326
+ @repo.save "continue work"
327
+ @mergestatus = @repo.include "feature"
328
+ end
329
+ it "returns false for conflicts" do
330
+ expect(@mergestatus).to be_false
331
+ end
332
+ end
333
+
334
+ end
335
+
336
+ describe "#include_into" do
337
+ include_context "temp repo"
338
+ it "calls current_branch, open, and include" do
339
+ current_branch = "feature"
340
+ other_branch = "master"
341
+
342
+ @repo.stub(:current_branch) { current_branch }
343
+ @repo.stub(:open)
344
+ @repo.stub(:include) { |branch| true }
345
+
346
+ @repo.include_into other_branch
347
+
348
+ expect(@repo).to have_received(:current_branch)
349
+ expect(@repo).to have_received(:open).with(other_branch)
350
+ expect(@repo).to have_received(:include).with(current_branch)
351
+ end
352
+ end
353
+
61
354
  end
@@ -1,5 +1,7 @@
1
1
  require "twit"
2
2
 
3
+ require "tmpdir"
4
+
3
5
  describe Twit do
4
6
 
5
7
  describe "::repo" do
@@ -9,4 +11,160 @@ describe Twit do
9
11
  end
10
12
  end
11
13
 
14
+ describe "::init" do
15
+
16
+ before do
17
+ @tmpdir = Dir.mktmpdir
18
+ end
19
+
20
+ after do
21
+ FileUtils.remove_entry @tmpdir
22
+ end
23
+
24
+ # Check if the current working directory is a git repository.
25
+ def expect_cwd_to_be_repo
26
+ `git status`
27
+ expect($?.exitstatus).to eq(0)
28
+ end
29
+
30
+ it "initializes given directory" do
31
+ Twit.init @tmpdir
32
+ Dir.chdir @tmpdir do
33
+ expect_cwd_to_be_repo
34
+ end
35
+ end
36
+
37
+ it "initializes the working directory by default" do
38
+ Dir.chdir @tmpdir do
39
+ Twit.init
40
+ expect_cwd_to_be_repo
41
+ end
42
+ end
43
+
44
+ it "returns a repo object" do
45
+ repo = Twit.init @tmpdir
46
+ expect(repo).to be_instance_of(Twit::Repo)
47
+ end
48
+
49
+ it "does not raise error on double-initialize" do
50
+ Twit.init @tmpdir
51
+ expect {
52
+ Twit.init @tmpdir
53
+ }.not_to raise_error
54
+ end
55
+
56
+ end
57
+
58
+ shared_context "stub repo" do
59
+ before do
60
+ @repo = double('repo')
61
+ Twit.stub(:repo) { @repo }
62
+ end
63
+ end
64
+
65
+ describe "::save" do
66
+ include_context "stub repo"
67
+ it "passes to default Repo object" do
68
+ message = "my test commit message"
69
+ expect(@repo).to receive(:save).with(message)
70
+ Twit.save message
71
+ end
72
+ end
73
+
74
+ describe "::saveas" do
75
+ include_context "stub repo"
76
+ it "passes to default Repo object" do
77
+ branch = "my_branch"
78
+ message = "my test commit message"
79
+ expect(@repo).to receive(:saveas).with(branch, message)
80
+ Twit.saveas branch, message
81
+ end
82
+ end
83
+
84
+ describe "::discard" do
85
+ include_context "stub repo"
86
+ it "passes to default Repo object" do
87
+ expect(@repo).to receive(:discard)
88
+ Twit.discard
89
+ end
90
+ end
91
+
92
+ describe "::list" do
93
+ include_context "stub repo"
94
+ it "passes to default Repo object" do
95
+ expect(@repo).to receive(:list)
96
+ Twit.list
97
+ end
98
+ end
99
+
100
+ describe "::open" do
101
+ include_context "stub repo"
102
+ it "passes to default Repo object" do
103
+ branch = "my_branch"
104
+ expect(@repo).to receive(:open).with(branch)
105
+ Twit.open branch
106
+ end
107
+ end
108
+
109
+ describe "::include" do
110
+ include_context "stub repo"
111
+ it "passes to default Repo object" do
112
+ branch = "my_branch"
113
+ expect(@repo).to receive(:include).with(branch)
114
+ Twit.include branch
115
+ end
116
+ end
117
+
118
+ describe "::include_into" do
119
+ include_context "stub repo"
120
+ it "passes to default Repo object" do
121
+ branch = "my_branch"
122
+ expect(@repo).to receive(:include_into).with(branch)
123
+ Twit.include_into branch
124
+ end
125
+ end
126
+
127
+ describe "::current_branch" do
128
+ include_context "stub repo"
129
+ it "passes to default Repo object" do
130
+ expect(@repo).to receive(:current_branch)
131
+ Twit.current_branch
132
+ end
133
+ end
134
+
135
+ describe "::nothing_to_commit?" do
136
+ include_context "stub repo"
137
+ it "passes to default Repo object" do
138
+ expect(@repo).to receive(:'nothing_to_commit?')
139
+ Twit.nothing_to_commit?
140
+ end
141
+ end
142
+
143
+ describe "::is_repo?" do
144
+ context "with no repo" do
145
+ before do
146
+ @tmpdir = Dir.mktmpdir
147
+ end
148
+ after do
149
+ FileUtils.remove_entry @tmpdir
150
+ end
151
+ it "returns false on empty directory" do
152
+ expect(Twit.is_repo? @tmpdir).to be_false
153
+ end
154
+ it "detects the current directory" do
155
+ Dir.chdir @tmpdir do
156
+ expect(Twit.is_repo?).to be_false
157
+ end
158
+ end
159
+ end
160
+ context "with a real repo" do
161
+ it "takes a directory as argument" do
162
+ expect(Twit.is_repo? @tmpdir).to be_true
163
+ end
164
+ it "detects the current directory" do
165
+ expect(Twit.is_repo?).to be_true
166
+ end
167
+ end
168
+ end
169
+
12
170
  end
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Twit::VERSION
9
9
  spec.authors = ["Ben Pringle"]
10
10
  spec.email = ["ben.pringle@gmail.com"]
11
- spec.description = %q{Create a simpler abstraction over the git command}
12
- spec.summary = %q{Simplified git wrapper}
11
+ spec.description = %q{Create a simpler abstraction over the Git command}
12
+ spec.summary = %q{Training wheels for Git}
13
13
  spec.homepage = "https://github.com/Pringley/twit"
14
14
  spec.license = "MIT"
15
15
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Pringle
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2013-08-02 00:00:00.000000000 Z
11
+ date: 2013-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -80,7 +80,7 @@ dependencies:
80
80
  - - '>='
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
- description: Create a simpler abstraction over the git command
83
+ description: Create a simpler abstraction over the Git command
84
84
  email:
85
85
  - ben.pringle@gmail.com
86
86
  executables:
@@ -99,13 +99,10 @@ files:
99
99
  - lib/twit.rb
100
100
  - lib/twit/cli.rb
101
101
  - lib/twit/error.rb
102
- - lib/twit/init.rb
103
102
  - lib/twit/repo.rb
104
- - lib/twit/repo/save.rb
105
103
  - lib/twit/version.rb
104
+ - spec/spec_helper.rb
106
105
  - spec/twit/cli_spec.rb
107
- - spec/twit/init_spec.rb
108
- - spec/twit/repo/save_spec.rb
109
106
  - spec/twit/repo_spec.rb
110
107
  - spec/twit_spec.rb
111
108
  - twit.gemspec
@@ -132,11 +129,10 @@ rubyforge_project:
132
129
  rubygems_version: 2.0.2
133
130
  signing_key:
134
131
  specification_version: 4
135
- summary: Simplified git wrapper
132
+ summary: Training wheels for Git
136
133
  test_files:
134
+ - spec/spec_helper.rb
137
135
  - spec/twit/cli_spec.rb
138
- - spec/twit/init_spec.rb
139
- - spec/twit/repo/save_spec.rb
140
136
  - spec/twit/repo_spec.rb
141
137
  - spec/twit_spec.rb
142
138
  has_rdoc:
@@ -1,21 +0,0 @@
1
- require 'open3'
2
- require 'twit/error'
3
-
4
- module Twit
5
-
6
- # Initialize a git repository in a directory. Return a Twit::Repo object
7
- # representing the new repository.
8
- #
9
- # If no argument is supplied, use the working directory.
10
- def self.init dir = nil
11
- dir ||= Dir.getwd
12
- Dir.chdir dir do
13
- stdout, stderr, status = Open3.capture3 "git init"
14
- if status != 0
15
- raise Error, stderr
16
- end
17
- end
18
- Repo.new dir
19
- end
20
-
21
- end
@@ -1,23 +0,0 @@
1
- require 'open3'
2
- require 'twit/error'
3
-
4
- module Twit
5
- class Repo
6
-
7
- # Update the snapshot of the current directory.
8
- def save message
9
- Dir.chdir @root do
10
- cmd = "git add --all && git commit -m \"#{message}\""
11
- stdout, stderr, status = Open3.capture3 cmd
12
- if status != 0
13
- if /nothing to commit/.match stdout
14
- raise NothingToCommitError
15
- else
16
- raise Error, stderr
17
- end
18
- end
19
- end
20
- end
21
-
22
- end
23
- end
@@ -1,43 +0,0 @@
1
- require 'tmpdir'
2
-
3
- require 'twit/init'
4
- require 'twit/repo'
5
-
6
- describe Twit do
7
- describe "::init" do
8
-
9
- before do
10
- @tmpdir = Dir.mktmpdir
11
- end
12
-
13
- after do
14
- FileUtils.remove_entry @tmpdir
15
- end
16
-
17
- # Check if the current working directory is a git repository.
18
- def expect_cwd_to_be_repo
19
- `git status`
20
- expect($?.exitstatus).to eq(0)
21
- end
22
-
23
- it "initializes given directory" do
24
- Twit.init @tmpdir
25
- Dir.chdir @tmpdir do
26
- expect_cwd_to_be_repo
27
- end
28
- end
29
-
30
- it "initializes the working directory by default" do
31
- Dir.chdir @tmpdir do
32
- Twit.init
33
- expect_cwd_to_be_repo
34
- end
35
- end
36
-
37
- it "returns a repo object" do
38
- repo = Twit.init @tmpdir
39
- expect(repo).to be_instance_of(Twit::Repo)
40
- end
41
-
42
- end
43
- end
@@ -1,55 +0,0 @@
1
- require 'tmpdir'
2
- require 'securerandom'
3
-
4
- require 'twit/repo'
5
- require 'twit/error'
6
-
7
- describe Twit::Repo, "#save" do
8
-
9
- before do
10
- @tmpdir = Dir.mktmpdir
11
- @repo = Twit.init @tmpdir
12
- @oldwd = Dir.getwd
13
- Dir.chdir @tmpdir
14
- end
15
-
16
- after do
17
- Dir.chdir @oldwd
18
- FileUtils.remove_entry @tmpdir
19
- end
20
-
21
- context "files in working tree" do
22
- before do
23
- 3.times do |i|
24
- File.open("file#{i}.txt", 'w') { |f| f.write("file#{i} contents\n") }
25
- end
26
- end
27
- it "commits the entire working tree" do
28
- @repo.save "created three files"
29
- expect(`git status`).to include('working directory clean')
30
- end
31
- it "makes a commit" do
32
- msg = "commit msg #{SecureRandom.hex(4)}"
33
- @repo.save msg
34
- expect(`git log`).to include(msg)
35
- end
36
- end
37
-
38
- it "raises error in new repository" do
39
- expect {
40
- @repo.save "trying to save"
41
- }.to raise_error(Twit::NothingToCommitError)
42
- end
43
-
44
- it "raises error with nothing to commit" do
45
- # First, make sure there's at least one commit on the log.
46
- File.open("foo", 'w') { |f| f.write("bar\n") }
47
- `git add foo && git commit -m "add foo"`
48
-
49
- # Now there should be nothing more to commit
50
- expect {
51
- @repo.save "trying to save"
52
- }.to raise_error(Twit::NothingToCommitError)
53
- end
54
-
55
- end