twit 0.0.2 → 0.0.3

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: 85fc88fe59a3282afb1f4e29af583408a35a3f29
4
- data.tar.gz: a30902725f73eeeb3ca2b84666ef23908292e265
3
+ metadata.gz: 987a48bc9202726f4c82223a921b870ff5e17b9a
4
+ data.tar.gz: 4c8e445811d5ad416ceaa6f8f934cb0657a531d1
5
5
  SHA512:
6
- metadata.gz: e82495bbc0018af33c6f33ae5ea9e14ebedd0a35c95ee6435fd6a0d0c227162dd76ebf93e70ab3f76687c481fd91d9979edf8024e50121da5c9da098b1d41812
7
- data.tar.gz: 4a295fe77e361dd9769a72fe10494c50d9be7dcc4c6a854a5c2bfbf944ab59a6075e8d9e1e5229eb22ee8b33efab87a5e7e6c38034b1f7b4fdcd670b89a4ab51
6
+ metadata.gz: eeda8ecd67d2080b1a96b3887b47065016f80bc537f21ee3660d6feddc1490198eff2369920a139d7daf1e98ba1428ca9974803ed39363809e74509ab0761eb0
7
+ data.tar.gz: c4913cd65bd75856cb39712542d6076de37085246a0049b10fdedae3ff20a160228a0618703197a947ba4d6c7419490cdf6f4248836ee3e4499b810c976bad71
data/.gitignore CHANGED
@@ -19,3 +19,4 @@ bin
19
19
  *~
20
20
  .*.swp
21
21
  Session.vim
22
+ .DS_Store
@@ -0,0 +1,13 @@
1
+ # Travis-CI Build for twit
2
+ # see travis-ci.org for details
3
+
4
+ language: ruby
5
+
6
+ rvm:
7
+ - 1.9.3
8
+ - 2.0.0
9
+
10
+ script:
11
+ - git config --global user.name 'The twit tests are as fragile as rugged'
12
+ - git config --global user.email 'fragile@tests.com'
13
+ - bundle exec rake
@@ -1,7 +1,105 @@
1
1
  # Twit: training wheels for Git
2
2
 
3
+ [![Build Status](https://travis-ci.org/Pringley/twit.png)](https://travis-ci.org/Pringley/twit)
4
+
5
+ Twit is an accessible version-control system based on the widely popular
6
+ [Git](http://git-scm.com) program.
7
+
8
+ ## Installation
9
+
10
+ To install, simply run:
11
+
12
+ gem install twit
13
+
14
+ (On some systems, this may require `sudo`.)
15
+
16
+ ## Usage
17
+
18
+ Version control allows you to keep track of **changes** to a project over time.
19
+
20
+ ### Example: roll back with `rewind`
21
+
22
+ Bob is working on an essay for English class. He creates a folder called
23
+ `myessay` and wants to track his changes.
24
+
25
+ First, he needs to turn that folder into a version-controlled **repository**.
26
+ This is done with the `init` command:
27
+
28
+ cd myessay
29
+ twit init
30
+
31
+ Then, Bob writes an initial draft of his essay in `myessay/essay.txt`. He can
32
+ then take a snapshot of his first draft using:
33
+
34
+ twit save
35
+
36
+ Each time he makes some edits to the paper, he runs `twit save` again.
37
+
38
+ One day, he decides to restructure the essay entirely. He makes several `save`s
39
+ with his big changes, deleting large amounts of text.
40
+
41
+ The next day he realizes that his restructuring effort was a huge mistake and
42
+ wants to recover the way his essay used to be. With version control, you can
43
+ roll back to any previous save point you've marked with `save`.
44
+
45
+ Bob uses `rewind` to go back three `save`s ago:
46
+
47
+ twit rewind 3
48
+
49
+ **Note:** using version control and frequent `save`s has a few benefits over
50
+ simply making a bunch of copies of your folder. One such benefit is space --
51
+ Twit (and its backend Git) only saves *changes* to your repository rather than
52
+ copying the entire thing. If you have a project with big files, this can save a
53
+ lot of hard disk space. Also, just typing `twit save` is faster than copying,
54
+ pasting, and renaming your new folder with some sort of label so you can find
55
+ it later. This encourages you to make more checkpoints, which gives you more
56
+ points to roll back to.
57
+
58
+ ### Example: using branches for a computer science class
59
+
60
+ **Branches** are like bookmarks at different save points. They allow you to
61
+ quickly switch between different versions of your repository.
62
+
63
+ In his computer science class, Bob has a three-part computer lab assignment. In
64
+ the first part, he must write a program, and in the second and third parts, he
65
+ must modify the program he wrote in two different ways.
66
+
67
+ Bob creates a folder called `cslab` and initializes it as a repository.
68
+
69
+ cd cslab
70
+ twit init
71
+
72
+ He completes the first part of the assignment. He now uses `saveas` to create a
73
+ new branch containing his completed part one code.
74
+
75
+ twit saveas part1
76
+
77
+ He then works on part two. Once that's working, he can create *another* branch:
78
+
79
+ twit saveas part2
80
+
81
+ Now, he has a problem -- the code he wrote for part two is making it really
82
+ hard to finish part three! That's okay -- it's easy to switch back to the part
83
+ one branch.
84
+
85
+ twit open part1
86
+
87
+ Now the repository is reverted back to the way it was in part one. (The part
88
+ two code is still saved in the `part2` branch -- it's just stored elsewhere for
89
+ now.)
90
+
91
+ From the part one code, Bob can finish the thirt part and run
92
+
93
+ twit saveas part3
94
+
95
+ Now there are three branches: `part1`, `part2`, and `part3`. Any of them can be
96
+ accessed with `twit open`, so Bob can easily show the professor his work.
97
+
98
+ ## Motivation (written for programmers)
99
+
3
100
  Twit is a wrapper for [Git](http://git-scm.com) that abstracts concepts that
4
- many newcomers find tedious or confusing.
101
+ many newcomers find tedious or confusing (at the cost of significant
102
+ flexibility).
5
103
 
6
104
  When explaining version control to newcomers, the benefits are often unclear
7
105
  amid the complicated rules and syntax of a powerful tool like Git. Twit aims to
@@ -11,39 +109,45 @@ version control system.
11
109
  For example, you can say that "version control allows you to save snapshots of
12
110
  your project history." However, in order to do this, you need to understand
13
111
  Git's two-step stage-then-commit workflow, with all its corner cases regarding
14
- new/deleted/moved files.
112
+ new/deleted/moved files. `git commit -a` ignores new files, `git add .` won't
113
+ stage file deletions, etc -- even `git add --all` has to be run from the root
114
+ of the working tree.
15
115
 
16
116
  Instead, Twit exposes a simple command to create a new commit with a snapshot
17
117
  of the repository:
18
118
 
19
119
  twit save
20
120
 
21
- This stages any changes (including new files and deletions) and creates a
121
+ This stages *all* changes (including new files and deletions) and creates a
22
122
  commit, prompting the user for a commit message if needed.
23
123
 
24
124
  To create a new branch (and save any changes to the new branch as well):
25
125
 
26
126
  twit saveas my_new_branch
27
127
 
128
+ If the user forgets to supply the branch argument and just uses `twit saveas`,
129
+ the CLI will nicely prompt them for a new branch name.
130
+
28
131
  This quick-and-easy approach allows a new user to get started using version
29
132
  control right away, without having to learn Git's minutiae.
30
133
 
31
134
  However, this simple program is **not** meant to replace Git for the power
32
135
  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
136
+ flexibility.
137
+
138
+ Git has a staging area for a reason -- atomic commits are important. Using
139
+ `twit save` will almost certainly result in commits with batches of changes
140
+ mangled together. *That's okay.* Twit isn't for professionals -- it's Git for
141
+ your mother to write a book, or for non-coders to add documentation to an open
142
+ source project. Best of all, **the repository is still Git underneath**, so
143
+ people can transition easily.
144
+
145
+ If you are a programmer, you should probably just buckle down and [learn
146
+ git](http://gitref.org). Instead, Twit serves as an introduction to version
147
+ control for users that would probably never learn Git, like writers or
36
148
  students.
37
149
 
38
- ## Installation
39
-
40
- To install, simply run:
41
-
42
- gem install twit
43
-
44
- (On some systems, this may require `sudo`.)
45
-
46
- ## Usage
150
+ ## Command Reference
47
151
 
48
152
  ### `init` -- create a new repository
49
153
 
@@ -51,7 +155,7 @@ To install, simply run:
51
155
 
52
156
  Initialize a new git repository in the current directory.
53
157
 
54
- Equivalent to: `git init`
158
+ Similar to: `git init`
55
159
 
56
160
  ### `save` -- commit all new changes to the current branch
57
161
 
@@ -61,28 +165,30 @@ Take a snapshot of all files in the directory.
61
165
 
62
166
  Any changes on the working tree will be committed to the current branch.
63
167
 
64
- Equivalent to: `git add --all && git commit -m <DESCRIBE_CHANGES>`
168
+ Similar to: `git add --all && git commit -m <DESCRIBE_CHANGES>`
65
169
 
66
170
  ### `saveas` -- commit all new changes to a new branch
67
171
 
68
172
  twit saveas [NEW_BRANCH] [DESCRIBE_CHANGES]
69
173
 
70
- Equivalent to: `git checkout -b <NEW_BRANCH>` then `twit save`
174
+ Similar to: `git checkout -b <NEW_BRANCH>` then `twit save`
71
175
 
72
176
  ### `open` -- open another branch
73
177
 
74
178
  twit open [BRANCH]
75
179
 
76
- Equivalent to: `git checkout <branch>`
180
+ Similar to: `git checkout <branch>`
181
+
182
+ Note: you can't use `twit open` to checkout a lone commit in detached HEAD
183
+ mode.
77
184
 
78
- ### `include` -- incorperate changes from another branch
185
+ ### `rewind` -- permanently rewind a branch
79
186
 
80
- twit include [OTHER_BRANCH]
187
+ twit rewind [AMOUNT]
81
188
 
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.)
189
+ **Permanently** move a branch back AMOUNT saves.
84
190
 
85
- Equivalent to: `git merge --no-ff --no-commit [OTHER_BRANCH]`
191
+ Similar to: `git reset --hard HEAD~<amount>`
86
192
 
87
193
  ### `discard` -- permanently delete unsaved changes
88
194
 
@@ -90,13 +196,13 @@ Equivalent to: `git merge --no-ff --no-commit [OTHER_BRANCH]`
90
196
 
91
197
  **Permanently** delete any unsaved changes to the current branch. Be careful!
92
198
 
93
- Equivalent to: `git reset --hard`
199
+ Similar to: `git reset --hard`
94
200
 
95
201
  ### `list` -- show all branches
96
202
 
97
203
  twit list
98
204
 
99
- Equivalent to: `git branch`
205
+ Similar to: `git branch`
100
206
 
101
207
  ## API
102
208
 
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ task :default => [:spec]
5
+
6
+ RSpec::Core::RakeTask.new
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'twit/gui'
3
+ Twit::GUI.main
@@ -2,7 +2,7 @@ require "twit/version"
2
2
  require "twit/repo"
3
3
  require "twit/error"
4
4
 
5
- require 'open3'
5
+ require "rugged"
6
6
 
7
7
  # This module exposes twit commands as methods.
8
8
  module Twit
@@ -27,12 +27,8 @@ module Twit
27
27
  return
28
28
  end
29
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
30
+ Rugged::Repository.init_at(dir)
31
+
36
32
  Repo.new dir
37
33
  end
38
34
 
@@ -41,17 +37,12 @@ module Twit
41
37
  # If no argument is supplied, use the working directory.
42
38
  def self.is_repo? dir = nil
43
39
  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
40
+ begin
41
+ root = Rugged::Repository.discover(dir)
42
+ rescue Rugged::RepositoryError
43
+ return false
54
44
  end
45
+ return true
55
46
  end
56
47
 
57
48
  # See {Twit::Repo#save}.
@@ -74,14 +65,9 @@ module Twit
74
65
  self.repo.open branch
75
66
  end
76
67
 
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
68
+ # See {Twit::Repo#rewind}.
69
+ def self.rewind amount
70
+ self.repo.rewind amount
85
71
  end
86
72
 
87
73
  # See {Twit::Repo#list}.
@@ -1,5 +1,6 @@
1
1
  require 'thor'
2
2
  require 'twit'
3
+ require 'twit/gui'
3
4
 
4
5
  module Twit
5
6
 
@@ -52,7 +53,7 @@ module Twit
52
53
  Twit.saveas branch, message
53
54
  rescue InvalidParameter => e
54
55
  if /already exists/.match e.message
55
- say "Cannot saveas to existing branch. See \"twit help include_into\""
56
+ say "Cannot saveas to existing branch. Try a git merge!"
56
57
  else
57
58
  say "Error: #{e.message}"
58
59
  end
@@ -95,43 +96,38 @@ module Twit
95
96
  end
96
97
 
97
98
  desc "include", "Integrate changes from another branch"
98
- # See {Twit::Repo#include}.
99
+ # Deprecated: use git merge instead.
99
100
  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."
114
- end
101
+ say "This function has been deprecated."
102
+ say "Use git merge instead!"
115
103
  end
116
104
 
117
105
  desc "include_into", "Integrate changes into another branch"
118
- # See {Twit::Repo#include}.
106
+ # Deprecated: use git merge instead.
119
107
  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
108
+ say "This function has been deprecated."
109
+ say "Use git merge instead!"
110
+ end
111
+
112
+ desc "rewind", "PERMANTENTLY rewind branch to a previous commit"
113
+ # See {Twit::Repo#rewind}.
114
+ def rewind amount
123
115
  begin
124
- @original = Twit.current_branch
125
- @success = Twit.include_into other_branch
116
+ unless Twit.nothing_to_commit?
117
+ return if no? "You have unsaved changes to your branch. Proceed?"
118
+ end
119
+ say "This branch will be rewound by #{amount} save points."
120
+ if yes? "Would you like to save a copy of these last #{amount} points?"
121
+ oldbranch = Twit.current_branch
122
+ newbranch = ask "Enter name for the copy branch:"
123
+ Twit.saveas newbranch
124
+ Twit.open oldbranch
125
+ elsif no? "These #{amount} changes may be lost forever! Are you sure?"
126
+ return
127
+ end
128
+ Twit.rewind amount.to_i
126
129
  rescue Error => e
127
130
  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
131
  end
136
132
  end
137
133
 
@@ -139,12 +135,28 @@ module Twit
139
135
  # See {Twit::Repo#discard}.
140
136
  def discard
141
137
  begin
138
+ say "All changes since last save will be reverted."
139
+ if yes? "Would you like to copy the changes in a different branch instead?"
140
+ oldbranch = Twit.current_branch
141
+ newbranch = ask "Enter name for the copy branch:"
142
+ Twit.saveas newbranch
143
+ Twit.open oldbranch
144
+ return
145
+ elsif no? "These changes will be lost forever! Are you sure?"
146
+ return
147
+ end
142
148
  Twit.discard
143
149
  rescue Error => e
144
150
  say "Error: #{e.message}"
145
151
  end
146
152
  end
147
153
 
154
+ desc "gui", "Start Twit's graphical user interface"
155
+ # See {Twit::GUI}.
156
+ def gui
157
+ Twit::GUI.main
158
+ end
159
+
148
160
  end
149
161
 
150
162
  end
@@ -10,6 +10,9 @@ module Twit
10
10
  # Raised when trying to commit nothing.
11
11
  class NothingToCommitError < Error; end
12
12
 
13
+ # Raised when trying to operate on a repository with unsaved changes.
14
+ class UnsavedChanges < Error; end
15
+
13
16
  # Raised when a command receives an invalid parameter.
14
17
  class InvalidParameter < Error; end
15
18
 
@@ -0,0 +1,10 @@
1
+ require 'twit'
2
+
3
+ module Twit::GUI
4
+
5
+ def main
6
+ puts "GUI not yet implemented."
7
+ end
8
+
9
+ module_function :main
10
+ end
@@ -1,4 +1,4 @@
1
- require 'open3'
1
+ require 'rugged'
2
2
  require 'twit/error'
3
3
 
4
4
  module Twit
@@ -16,60 +16,69 @@ module Twit
16
16
  # raise {Twit::NotARepositoryError}.
17
17
  def initialize root = nil
18
18
  if root.nil?
19
- stdout, stderr, status = Open3.capture3 "git rev-parse --show-toplevel"
20
- if status != 0
21
- case stderr
22
- when /Not a git repository/
23
- raise NotARepositoryError
24
- else
25
- raise Error, stderr
26
- end
19
+ begin
20
+ root = Rugged::Repository.discover(Dir.getwd)
21
+ rescue Rugged::RepositoryError
22
+ raise NotARepositoryError
27
23
  end
28
- root = stdout.strip
29
24
  end
30
- @root = root
25
+ @git = Rugged::Repository.new(root)
26
+ @root = @git.workdir
31
27
  end
32
28
 
33
29
  # Update the snapshot of the current directory.
34
30
  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
31
+ raise NothingToCommitError if nothing_to_commit?
32
+ @git.index.add_all
33
+ @git.index.write
34
+ usr = {name: @git.config['user.name'],
35
+ email: @git.config['user.email'],
36
+ time: Time.now}
37
+ opt = {}
38
+ opt[:tree] = @git.index.write_tree
39
+ opt[:author] = usr
40
+ opt[:committer] = usr
41
+ opt[:message] = message
42
+ opt[:parents] = unless @git.empty?
43
+ begin
44
+ [@git.head.target].compact
45
+ rescue Rugged::ReferenceError
46
+ []
47
+ end
48
+ else [] end
49
+ opt[:update_ref] = 'HEAD'
50
+ Rugged::Commit.create(@git, opt)
48
51
  end
49
52
 
50
53
  # Save the current state of the repository to a new branch.
51
54
  def saveas newbranch, message = nil
52
55
  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
56
+ begin
57
+ if @git.empty?
58
+ # For an empty repo, we can "create a new branch" by setting HEAD to
59
+ # a symbolic reference to the new branch. Then, the next commit will
60
+ # create that branch (instead of master).
61
+ Rugged::Reference.create(@git, 'HEAD',
62
+ "refs/heads/#{newbranch}", true)
63
+ else
64
+ # For a non-empty repo, we just create a new branch and switch to it.
65
+ branch = @git.create_branch newbranch
66
+ @git.head = branch.canonical_name
67
+ end
68
+ rescue Rugged::ReferenceError => error
69
+ case error.message
70
+ when /is not valid/
71
+ raise InvalidParameter, "#{newbranch} is not a valid branch name"
72
+ when /already exists/
73
+ raise InvalidParameter, "#{newbranch} already exists"
74
+ else
75
+ raise Error, "Internal Rugged error: #{error.message}"
68
76
  end
69
77
  end
78
+
70
79
  # Next, save any working changes.
71
80
  begin
72
- Twit.save message
81
+ save message
73
82
  rescue NothingToCommitError
74
83
  # New changes are not required for saveas.
75
84
  end
@@ -77,127 +86,51 @@ module Twit
77
86
 
78
87
  # Return an Array of branches in the repo.
79
88
  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
89
+ Rugged::Branch.each_name(@git, :local).to_a
99
90
  end
100
91
 
101
92
  # Return the current branch.
102
93
  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
94
+ ref = Rugged::Reference.lookup(@git, 'HEAD').resolve
95
+ if not ref.branch?
96
+ raise Error, "Not currently on a branch."
117
97
  end
98
+ ref.name.split('/').last
118
99
  end
119
100
 
120
101
  # Open a branch.
121
102
  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
103
+ ref = Rugged::Branch.lookup(@git, branch)
104
+ raise InvalidParameter, "#{branch} is not a branch" if ref.nil?
105
+ @git.head = ref.canonical_name
106
+ @git.reset('HEAD', :hard)
136
107
  end
137
108
 
138
109
  # Clean the working directory (permanently deletes changes!!!).
139
110
  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
111
+ # First, add all files to the index. (Otherwise, we won't discard new
112
+ # files.) Then, hard reset to revert to the last saved state.
113
+ @git.index.add_all
114
+ @git.index.write
115
+ @git.reset('HEAD', :hard)
175
116
  end
176
117
 
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)
118
+ # Create a new branch at the specified commit id (may permanently lose
119
+ # commits!!!).
120
+ def rewind amount
121
+ raise UnsavedChanges unless nothing_to_commit?
122
+ raise ArgumentError, "Expected integer amount" unless amount.is_a? Integer
123
+ @git.index.add_all
124
+ @git.index.write
125
+ @git.reset("HEAD~#{amount}", :hard)
183
126
  end
184
127
 
185
128
  # Return true if there is nothing new to commit.
186
129
  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
130
+ @git.status do |file, status|
131
+ return false unless status.empty?
200
132
  end
133
+ return true
201
134
  end
202
135
 
203
136
  end
@@ -1,4 +1,4 @@
1
1
  module Twit
2
2
  # Gem version
3
- VERSION = "0.0.2"
3
+ VERSION = "0.0.3"
4
4
  end
@@ -28,6 +28,7 @@ describe Twit::Repo do
28
28
  end
29
29
 
30
30
  it "works with a directory argument" do
31
+ `git init #{@tmpdir}` # only works on initialized repo
31
32
  repo = Twit::Repo.new @tmpdir
32
33
  expect_root repo.root
33
34
  end
@@ -271,84 +272,25 @@ describe Twit::Repo do
271
272
 
272
273
  end
273
274
 
274
- describe "#include" do
275
-
275
+ describe "#rewind" do
276
276
  include_context "temp repo"
277
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"
278
+ before do
279
+ # Create two commits.
280
+ File.open("spam", 'w') { |f| f.write("first\n") }
281
+ @repo.save "first commit"
282
+ @first_id = `git rev-parse HEAD`.strip
283
+ File.open("spam", 'w') { |f| f.write("second\n") }
284
+ @repo.save "second commit"
285
+ @second_id = `git rev-parse HEAD`.strip
286
+ # Rewind to the first.
287
+ @repo.rewind 1
313
288
  end
314
289
 
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
290
+ it "sets HEAD to the first commit" do
291
+ expect(`git rev-parse HEAD`.strip).to eq(@first_id)
332
292
  end
333
293
 
334
294
  end
335
295
 
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
-
354
296
  end
@@ -106,21 +106,12 @@ describe Twit do
106
106
  end
107
107
  end
108
108
 
109
- describe "::include" do
109
+ describe "::rewind" do
110
110
  include_context "stub repo"
111
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
112
+ amount = 1
113
+ expect(@repo).to receive(:rewind).with(amount)
114
+ Twit.rewind amount
124
115
  end
125
116
  end
126
117
 
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.require_paths = ["lib"]
21
21
 
22
22
  spec.add_runtime_dependency "thor"
23
+ spec.add_runtime_dependency "rugged"
23
24
 
24
25
  spec.add_development_dependency "bundler", "~> 1.3"
25
26
  spec.add_development_dependency "rake"
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.2
4
+ version: 0.0.3
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-14 00:00:00.000000000 Z
11
+ date: 2013-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rugged
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -85,24 +99,27 @@ email:
85
99
  - ben.pringle@gmail.com
86
100
  executables:
87
101
  - twit
102
+ - twit-gui
88
103
  extensions: []
89
104
  extra_rdoc_files: []
90
105
  files:
91
106
  - .gitignore
92
107
  - .rspec
108
+ - .travis.yml
93
109
  - Gemfile
94
110
  - IDEAS.txt
95
111
  - LICENSE.txt
96
112
  - README.markdown
97
113
  - Rakefile
98
114
  - exe/twit
115
+ - exe/twit-gui
99
116
  - lib/twit.rb
100
117
  - lib/twit/cli.rb
101
118
  - lib/twit/error.rb
119
+ - lib/twit/gui.rb
102
120
  - lib/twit/repo.rb
103
121
  - lib/twit/version.rb
104
122
  - spec/spec_helper.rb
105
- - spec/twit/cli_spec.rb
106
123
  - spec/twit/repo_spec.rb
107
124
  - spec/twit_spec.rb
108
125
  - twit.gemspec
@@ -126,13 +143,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
143
  version: '0'
127
144
  requirements: []
128
145
  rubyforge_project:
129
- rubygems_version: 2.0.2
146
+ rubygems_version: 2.0.3
130
147
  signing_key:
131
148
  specification_version: 4
132
149
  summary: Training wheels for Git
133
150
  test_files:
134
151
  - spec/spec_helper.rb
135
- - spec/twit/cli_spec.rb
136
152
  - spec/twit/repo_spec.rb
137
153
  - spec/twit_spec.rb
138
154
  has_rdoc:
@@ -1,55 +0,0 @@
1
- require "twit/cli"
2
-
3
- describe Twit::CLI do
4
-
5
- before do
6
- @cli = Twit::CLI.new
7
-
8
- # Mock out Twit library to avoid accidentally clobbering anything during
9
- # testing.
10
- stub_const("Twit", double('Twit'))
11
- end
12
-
13
- describe "init" do
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
22
- end
23
- end
24
-
25
- describe "save" do
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
37
-
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]
45
- end
46
- end
47
-
48
- describe "discard" do
49
- it "calls Twit.discard" do
50
- expect(Twit).to receive(:discard)
51
- @cli.invoke :discard
52
- end
53
- end
54
-
55
- end