twit 0.0.1 → 0.0.2

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.
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