twit 0.0.2 → 0.0.3
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 +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
|
+
[](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
|