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 +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +13 -0
- data/README.markdown +132 -26
- data/Rakefile +5 -0
- data/exe/twit-gui +3 -0
- data/lib/twit.rb +11 -25
- data/lib/twit/cli.rb +42 -30
- data/lib/twit/error.rb +3 -0
- data/lib/twit/gui.rb +10 -0
- data/lib/twit/repo.rb +74 -141
- data/lib/twit/version.rb +1 -1
- data/spec/twit/repo_spec.rb +14 -72
- data/spec/twit_spec.rb +4 -13
- data/twit.gemspec +1 -0
- metadata +21 -5
- data/spec/twit/cli_spec.rb +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 987a48bc9202726f4c82223a921b870ff5e17b9a
|
4
|
+
data.tar.gz: 4c8e445811d5ad416ceaa6f8f934cb0657a531d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eeda8ecd67d2080b1a96b3887b47065016f80bc537f21ee3660d6feddc1490198eff2369920a139d7daf1e98ba1428ca9974803ed39363809e74509ab0761eb0
|
7
|
+
data.tar.gz: c4913cd65bd75856cb39712542d6076de37085246a0049b10fdedae3ff20a160228a0618703197a947ba4d6c7419490cdf6f4248836ee3e4499b810c976bad71
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
@@ -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
|
data/README.markdown
CHANGED
@@ -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
|
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.
|
34
|
-
|
35
|
-
|
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
|
-
##
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
### `
|
185
|
+
### `rewind` -- permanently rewind a branch
|
79
186
|
|
80
|
-
twit
|
187
|
+
twit rewind [AMOUNT]
|
81
188
|
|
82
|
-
|
83
|
-
can resolve any conflicts and then run `twit save` themselves.)
|
189
|
+
**Permanently** move a branch back AMOUNT saves.
|
84
190
|
|
85
|
-
|
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
|
-
|
199
|
+
Similar to: `git reset --hard`
|
94
200
|
|
95
201
|
### `list` -- show all branches
|
96
202
|
|
97
203
|
twit list
|
98
204
|
|
99
|
-
|
205
|
+
Similar to: `git branch`
|
100
206
|
|
101
207
|
## API
|
102
208
|
|
data/Rakefile
CHANGED
data/exe/twit-gui
ADDED
data/lib/twit.rb
CHANGED
@@ -2,7 +2,7 @@ require "twit/version"
|
|
2
2
|
require "twit/repo"
|
3
3
|
require "twit/error"
|
4
4
|
|
5
|
-
require
|
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
|
-
|
31
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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#
|
78
|
-
def self.
|
79
|
-
self.repo.
|
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}.
|
data/lib/twit/cli.rb
CHANGED
@@ -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.
|
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
|
-
#
|
99
|
+
# Deprecated: use git merge instead.
|
99
100
|
def include other_branch = nil
|
100
|
-
|
101
|
-
|
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
|
-
#
|
106
|
+
# Deprecated: use git merge instead.
|
119
107
|
def include_into other_branch = nil
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
-
|
125
|
-
|
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
|
data/lib/twit/error.rb
CHANGED
@@ -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
|
|
data/lib/twit/gui.rb
ADDED
data/lib/twit/repo.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
@
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
-
#
|
178
|
-
#
|
179
|
-
def
|
180
|
-
|
181
|
-
|
182
|
-
|
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
|
-
|
188
|
-
|
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
|
data/lib/twit/version.rb
CHANGED
data/spec/twit/repo_spec.rb
CHANGED
@@ -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 "#
|
275
|
-
|
275
|
+
describe "#rewind" do
|
276
276
|
include_context "temp repo"
|
277
277
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
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
|
-
|
316
|
-
|
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
|
data/spec/twit_spec.rb
CHANGED
@@ -106,21 +106,12 @@ describe Twit do
|
|
106
106
|
end
|
107
107
|
end
|
108
108
|
|
109
|
-
describe "::
|
109
|
+
describe "::rewind" do
|
110
110
|
include_context "stub repo"
|
111
111
|
it "passes to default Repo object" do
|
112
|
-
|
113
|
-
expect(@repo).to receive(:
|
114
|
-
Twit.
|
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
|
|
data/twit.gemspec
CHANGED
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.
|
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-
|
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.
|
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:
|
data/spec/twit/cli_spec.rb
DELETED
@@ -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
|