gitloop 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.
- data/Gemfile +4 -0
- data/Gemfile.lock +61 -0
- data/LICENSE +20 -0
- data/README.markdown +49 -0
- data/bin/git-loop +59 -0
- data/bin/gitloop +59 -0
- data/doc/AFTER.png +0 -0
- data/doc/BEFORE.png +0 -0
- data/doc/TODO.txt +31 -0
- data/doc/dev_notes.txt +6 -0
- data/features/errors.feature +29 -0
- data/features/happy_path.feature +58 -0
- data/features/sandbox.feature +98 -0
- data/features/shell_command.feature +14 -0
- data/features/step_definitions/git_steps.rb +21 -0
- data/features/step_definitions/gitloop_steps.rb +49 -0
- data/features/step_definitions/sandbox_steps.rb +63 -0
- data/features/support/benchmarking.rb +24 -0
- data/features/support/env.rb +20 -0
- data/features/support/fixtures/sandbox-with-staged-changes.zip +0 -0
- data/features/support/fixtures/sandbox-with-unstaged-changes.zip +0 -0
- data/features/support/fixtures/sandbox.zip +0 -0
- data/features/support/git.rb +21 -0
- data/features/support/regexp.rb +23 -0
- data/lib/gitloop.rb +8 -0
- data/lib/gitloop/git_utils.rb +33 -0
- data/lib/gitloop/looper.rb +59 -0
- data/lib/gitloop/version.rb +3 -0
- metadata +161 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
gitloop (0.0.3)
|
5
|
+
methadone
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
aruba (0.4.11)
|
11
|
+
childprocess (>= 0.2.3)
|
12
|
+
cucumber (>= 1.1.1)
|
13
|
+
ffi (>= 1.0.11)
|
14
|
+
rspec (>= 2.7.0)
|
15
|
+
builder (3.0.0)
|
16
|
+
childprocess (0.3.1)
|
17
|
+
ffi (~> 1.0.6)
|
18
|
+
cucumber (1.1.4)
|
19
|
+
builder (>= 2.1.2)
|
20
|
+
diff-lcs (>= 1.1.2)
|
21
|
+
gherkin (~> 2.7.1)
|
22
|
+
json (>= 1.4.6)
|
23
|
+
term-ansicolor (>= 1.0.6)
|
24
|
+
diff-lcs (1.1.3)
|
25
|
+
ffi (1.0.11)
|
26
|
+
gherkin (2.7.7)
|
27
|
+
json (>= 1.4.6)
|
28
|
+
guard (1.0.0)
|
29
|
+
ffi (>= 0.5.0)
|
30
|
+
thor (~> 0.14.6)
|
31
|
+
guard-cucumber (0.7.5)
|
32
|
+
cucumber (>= 0.10)
|
33
|
+
guard (>= 0.8.3)
|
34
|
+
json (1.6.5)
|
35
|
+
methadone (0.5.1)
|
36
|
+
bundler
|
37
|
+
rake (0.9.2.2)
|
38
|
+
rdoc (3.12)
|
39
|
+
json (~> 1.4)
|
40
|
+
rspec (2.8.0)
|
41
|
+
rspec-core (~> 2.8.0)
|
42
|
+
rspec-expectations (~> 2.8.0)
|
43
|
+
rspec-mocks (~> 2.8.0)
|
44
|
+
rspec-core (2.8.0)
|
45
|
+
rspec-expectations (2.8.0)
|
46
|
+
diff-lcs (~> 1.1.2)
|
47
|
+
rspec-mocks (2.8.0)
|
48
|
+
ruby_gntp (0.3.4)
|
49
|
+
term-ansicolor (1.0.7)
|
50
|
+
thor (0.14.6)
|
51
|
+
|
52
|
+
PLATFORMS
|
53
|
+
ruby
|
54
|
+
|
55
|
+
DEPENDENCIES
|
56
|
+
aruba
|
57
|
+
gitloop!
|
58
|
+
guard-cucumber
|
59
|
+
rake (~> 0.9.2)
|
60
|
+
rdoc
|
61
|
+
ruby_gntp
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Alain Ravet
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
[gitloop](http://rubygems.org/gems/gitloop)
|
2
|
+
=======
|
3
|
+
|
4
|
+
|
5
|
+
<table>
|
6
|
+
<tr style="line-height:5em">
|
7
|
+
<td colspan="3" style="text-align:center;">
|
8
|
+
<code style="font-size:1.6em;padding:20px 40px;background:lightyellow">
|
9
|
+
git <b>loop</b> master~4 master~2 <b>-m</b> "let's group them"
|
10
|
+
</code>
|
11
|
+
</td>
|
12
|
+
</tr>
|
13
|
+
<tr>
|
14
|
+
<td width="42%" style="text-align:center"> <img src="https://github.com/alainravet/gitloop/raw/master/doc/BEFORE.png"/></td>
|
15
|
+
<td width="6%" style="text-align:center;font-size:4em;">→</td>
|
16
|
+
<td width="42%" style="text-align:center"> <img src="https://github.com/alainravet/gitloop/raw/master/doc/AFTER.png" /></td>
|
17
|
+
</tr>
|
18
|
+
</table>
|
19
|
+
|
20
|
+
|
21
|
+
The same results would be obtained by any of :
|
22
|
+
|
23
|
+
$ git loop 7355c79 6ffd9 -m "a logical group"
|
24
|
+
$ git loop second c4 -m "a logical group"
|
25
|
+
$ git loop c2 6ffd9 -m "a logical group"
|
26
|
+
|
27
|
+
|
28
|
+
Installation :
|
29
|
+
-------------
|
30
|
+
$ gem install gitloop
|
31
|
+
|
32
|
+
#### Report bugs to <https://github.com/alainravet/alainravet/>
|
33
|
+
|
34
|
+
--------------------------------------------------------------------------------
|
35
|
+
|
36
|
+
== Note on Patches/Pull Requests
|
37
|
+
|
38
|
+
* Fork the project.
|
39
|
+
* Make your feature addition or bug fix.
|
40
|
+
* Add tests for it. This is important so I don't break it in a
|
41
|
+
future version unintentionally.
|
42
|
+
* Commit, do not mess with rakefile, version, or history.
|
43
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
44
|
+
* Send me a pull request. Bonus points for topic branches.
|
45
|
+
|
46
|
+
== Copyright
|
47
|
+
|
48
|
+
Copyright (c) 2012 Alain Ravet. See LICENSE for details.
|
49
|
+
·
|
data/bin/git-loop
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'methadone'
|
5
|
+
require File.dirname(__FILE__) + '/../lib/gitloop'
|
6
|
+
|
7
|
+
include Methadone::Main
|
8
|
+
|
9
|
+
main do |from_ref, to_ref|
|
10
|
+
all_parameters_provided = from_ref && to_ref && options[:message]
|
11
|
+
if all_parameters_provided
|
12
|
+
begin
|
13
|
+
Gitloop::Looper.make_loop_for_references_or_messages(from_ref, to_ref, options[:message])
|
14
|
+
rescue Gitloop::GitError => e
|
15
|
+
puts "*** ERROR : #{e}"
|
16
|
+
end
|
17
|
+
else
|
18
|
+
puts @banner
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
on("-m MESSAGE", "--message", "commit message for the loop") do |m|
|
23
|
+
options[:message] = m
|
24
|
+
end
|
25
|
+
|
26
|
+
@banner = <<-DOC
|
27
|
+
Usage: git loop <from_ref> <to_ref> -m "<message>"
|
28
|
+
where <*_ref> is either
|
29
|
+
- a commit reference (SHA, branch name, etc..) or
|
30
|
+
- a commit message part
|
31
|
+
|
32
|
+
|
33
|
+
example:
|
34
|
+
|
35
|
+
+-----------------------------------------------------------------+
|
36
|
+
| $ git loop "c2" "the third" -m "a logical group" |
|
37
|
+
| or $ git loop 7355c79 bab916a -m "a logical group" |
|
38
|
+
+-----------------------------------------------------------------+
|
39
|
+
BEFORE AFTER
|
40
|
+
-------------------------------------------------------------------
|
41
|
+
* 69fd256 c6
|
42
|
+
* 377c650 c5 five - fifth commit
|
43
|
+
* 9f31be9 c4 not the third commit
|
44
|
+
* edadbb7 c6 ----------> * 845c17e *** a logical group
|
45
|
+
* da55377 c5 five - fifth commit |\\
|
46
|
+
* 6fddd9c c4 not the third commit [ | * bab916a c3 the third commit ]
|
47
|
+
[ * bab916a c3 the third commit ] [ | * 7355c79 c2 the second commit ]
|
48
|
+
[ * 7355c79 c2 the second commit ] |/
|
49
|
+
* 91182df c1 * 91182df c1
|
50
|
+
|
51
|
+
DOC
|
52
|
+
description " combine `git merge --no--ff ..` and `git rebase ..` to regroup commits sequences in \"loops\"" +
|
53
|
+
"\n\n" +
|
54
|
+
@banner
|
55
|
+
|
56
|
+
|
57
|
+
version Gitloop::VERSION
|
58
|
+
|
59
|
+
go!
|
data/bin/gitloop
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'methadone'
|
5
|
+
require File.dirname(__FILE__) + '/../lib/gitloop'
|
6
|
+
|
7
|
+
include Methadone::Main
|
8
|
+
|
9
|
+
main do |from_ref, to_ref|
|
10
|
+
all_parameters_provided = from_ref && to_ref && options[:message]
|
11
|
+
if all_parameters_provided
|
12
|
+
begin
|
13
|
+
Gitloop::Looper.make_loop_for_references_or_messages(from_ref, to_ref, options[:message])
|
14
|
+
rescue Gitloop::GitError => e
|
15
|
+
puts "*** ERROR : #{e}"
|
16
|
+
end
|
17
|
+
else
|
18
|
+
puts @banner
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
on("-m MESSAGE", "--message", "commit message for the loop") do |m|
|
23
|
+
options[:message] = m
|
24
|
+
end
|
25
|
+
|
26
|
+
@banner = <<-DOC
|
27
|
+
Usage: git loop <from_ref> <to_ref> -m "<message>"
|
28
|
+
where <*_ref> is either
|
29
|
+
- a commit reference (SHA, branch name, etc..) or
|
30
|
+
- a commit message part
|
31
|
+
|
32
|
+
|
33
|
+
example:
|
34
|
+
|
35
|
+
+-----------------------------------------------------------------+
|
36
|
+
| $ git loop "c2" "the third" -m "a logical group" |
|
37
|
+
| or $ git loop 7355c79 bab916a -m "a logical group" |
|
38
|
+
+-----------------------------------------------------------------+
|
39
|
+
BEFORE AFTER
|
40
|
+
-------------------------------------------------------------------
|
41
|
+
* 69fd256 c6
|
42
|
+
* 377c650 c5 five - fifth commit
|
43
|
+
* 9f31be9 c4 not the third commit
|
44
|
+
* edadbb7 c6 ----------> * 845c17e *** a logical group
|
45
|
+
* da55377 c5 five - fifth commit |\\
|
46
|
+
* 6fddd9c c4 not the third commit [ | * bab916a c3 the third commit ]
|
47
|
+
[ * bab916a c3 the third commit ] [ | * 7355c79 c2 the second commit ]
|
48
|
+
[ * 7355c79 c2 the second commit ] |/
|
49
|
+
* 91182df c1 * 91182df c1
|
50
|
+
|
51
|
+
DOC
|
52
|
+
description " combine `git merge --no--ff ..` and `git rebase ..` to regroup commits sequences in \"loops\"" +
|
53
|
+
"\n\n" +
|
54
|
+
@banner
|
55
|
+
|
56
|
+
|
57
|
+
version Gitloop::VERSION
|
58
|
+
|
59
|
+
go!
|
data/doc/AFTER.png
ADDED
Binary file
|
data/doc/BEFORE.png
ADDED
Binary file
|
data/doc/TODO.txt
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
TESTS FOR ERROR CASES
|
2
|
+
|
3
|
+
error result
|
4
|
+
- exception
|
5
|
+
- error message in the output
|
6
|
+
------------------------------------------------------------------------
|
7
|
+
|
8
|
+
|
9
|
+
-f -t : allow using SHA for 1/2 of the args
|
10
|
+
|
11
|
+
ABORT if
|
12
|
+
- unstaged changes
|
13
|
+
- not on a branch (current_branch.nil?)
|
14
|
+
- <from> == very 1st commit
|
15
|
+
- missing parameters
|
16
|
+
- `git` not found
|
17
|
+
- not in a git directory
|
18
|
+
|
19
|
+
after ABORT,
|
20
|
+
* reset --hard to original situation + clean if anything fails
|
21
|
+
* explain
|
22
|
+
|
23
|
+
|
24
|
+
edge case :
|
25
|
+
- empty repository
|
26
|
+
|
27
|
+
def current_SHA
|
28
|
+
`git rev-parse HEAD`
|
29
|
+
end
|
30
|
+
|
31
|
+
|
data/doc/dev_notes.txt
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
Feature: Errors detection and handling
|
2
|
+
|
3
|
+
As a developer
|
4
|
+
In order to improve my git history structure
|
5
|
+
I want to create a few loops via git merge and git rebase
|
6
|
+
|
7
|
+
|
8
|
+
Background:
|
9
|
+
Given the sandbox is setup
|
10
|
+
Given I cd to "sandbox"
|
11
|
+
|
12
|
+
|
13
|
+
Scenario Outline: in Ruby, create a loop from the exact SHAs
|
14
|
+
|
15
|
+
|
16
|
+
When I run `git-loop <from_ref> <to_ref> -m "fail"`
|
17
|
+
|
18
|
+
Then the repo is in its original state
|
19
|
+
And there are no unstaged nor staged changes
|
20
|
+
And the current branch is "master"
|
21
|
+
|
22
|
+
Scenarios: invalid or unknown SHAs
|
23
|
+
| from_ref | to_ref |
|
24
|
+
| 91182df | a059392 |
|
25
|
+
| 1111111 | a059392 |
|
26
|
+
|
27
|
+
Scenarios: wrong order
|
28
|
+
| from_ref | to_ref |
|
29
|
+
| a059392 | 8aab2de |
|
@@ -0,0 +1,58 @@
|
|
1
|
+
Feature: Happy path (no problems, no edge case)
|
2
|
+
|
3
|
+
As a developer
|
4
|
+
In order to improve my git history structure
|
5
|
+
I want to create a few loops via git merge and git rebase
|
6
|
+
|
7
|
+
|
8
|
+
Background:
|
9
|
+
Given the sandbox is setup
|
10
|
+
Given I cd to "sandbox"
|
11
|
+
|
12
|
+
#--------------------------------------------------------------------------------
|
13
|
+
|
14
|
+
Scenario Outline: the many ways to create the same loop
|
15
|
+
|
16
|
+
|
17
|
+
When I run `<command>`
|
18
|
+
|
19
|
+
Then the git log graph matches:
|
20
|
+
"""
|
21
|
+
* <a_sha> c6
|
22
|
+
* <a_sha> c5 five - fifth commit
|
23
|
+
* <a_sha> *** hellboy was here
|
24
|
+
|\
|
25
|
+
| * 6fddd9c c4 not the third commit
|
26
|
+
| * bab916a c3 the third commit
|
27
|
+
| * 7355c79 c2 the second commit
|
28
|
+
|/
|
29
|
+
* 91182df c1
|
30
|
+
"""
|
31
|
+
And the current branch is "master"
|
32
|
+
|
33
|
+
Scenarios: pure SHAs or pure messages snippets
|
34
|
+
| command |
|
35
|
+
| git-loop 7355c79 6fddd9c -m "hellboy was here" |
|
36
|
+
| git-loop master~4 master~2 -m "hellboy was here" |
|
37
|
+
| git-loop "c2" "c4" -m "hellboy was here" |
|
38
|
+
| git-loop "second commit" 6fddd9c -m "hellboy was here" |
|
39
|
+
|
40
|
+
|
41
|
+
#--------------------------------------------------------------------------------
|
42
|
+
|
43
|
+
Scenario: comment can contain quote
|
44
|
+
When I run `git-loop 7355c79 6fddd9c -m "let's do it"`
|
45
|
+
|
46
|
+
Then the current branch is "master"
|
47
|
+
And the git log graph matches:
|
48
|
+
"""
|
49
|
+
* <a_sha> c6
|
50
|
+
* <a_sha> c5 five - fifth commit
|
51
|
+
* <a_sha> *** let's do it
|
52
|
+
|\
|
53
|
+
| * 6fddd9c c4 not the third commit
|
54
|
+
| * bab916a c3 the third commit
|
55
|
+
| * 7355c79 c2 the second commit
|
56
|
+
|/
|
57
|
+
* 91182df c1
|
58
|
+
"""
|
@@ -0,0 +1,98 @@
|
|
1
|
+
Feature: playing in the sanbox
|
2
|
+
As a developer
|
3
|
+
In order to test `gitloop` thoroughly
|
4
|
+
I want to Install in tmp/sandbox different prepared git repositories
|
5
|
+
that cover all the possible combinations of staged/unstanged
|
6
|
+
files
|
7
|
+
|
8
|
+
|
9
|
+
Background:
|
10
|
+
* I cleared the sandbox directory
|
11
|
+
* a directory named "sandbox" should not exist
|
12
|
+
|
13
|
+
|
14
|
+
#############################
|
15
|
+
# Test the git status output :
|
16
|
+
#############################
|
17
|
+
|
18
|
+
Scenario: checking the clean sandbox
|
19
|
+
|
20
|
+
Given I unzip the clean git repo in the sandbox
|
21
|
+
* I cd to "sandbox"
|
22
|
+
Then the git status is empty
|
23
|
+
|
24
|
+
|
25
|
+
Scenario: checking the sandbox with some unstaged changes
|
26
|
+
|
27
|
+
Given I unzip the git repo with unstaged changes in the sandbox
|
28
|
+
* I cd to "sandbox"
|
29
|
+
Then the git status is:
|
30
|
+
"""
|
31
|
+
M f1
|
32
|
+
?? unstaged-new-file
|
33
|
+
"""
|
34
|
+
|
35
|
+
Scenario: checking the sandbox with some staged changes
|
36
|
+
|
37
|
+
Given I unzip the git repo with staged changes in the sandbox
|
38
|
+
* I cd to "sandbox"
|
39
|
+
Then the git status is:
|
40
|
+
"""
|
41
|
+
M f1
|
42
|
+
A unstaged-new-file
|
43
|
+
"""
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
#############################
|
48
|
+
# Test the git log graph output :
|
49
|
+
#############################
|
50
|
+
|
51
|
+
Scenario Outline: the sandbox is initialized by unzipping a model git repository
|
52
|
+
|
53
|
+
Given I unzip the <repository> in the sandbox
|
54
|
+
* I cd to "sandbox"
|
55
|
+
|
56
|
+
Then the repo is in its original state
|
57
|
+
Then HEAD starts with edadbb7
|
58
|
+
* the current branch is "master"
|
59
|
+
* there are <outcome>
|
60
|
+
|
61
|
+
Examples:
|
62
|
+
| repository | outcome |
|
63
|
+
| clean git repo | no unstaged nor staged changes |
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
Scenario Outline: the sandbox is initialized by unzipping a model git repository
|
68
|
+
|
69
|
+
Given I unzip the <repository> in the sandbox
|
70
|
+
* I cd to "sandbox"
|
71
|
+
|
72
|
+
Then the git log graph is:
|
73
|
+
"""
|
74
|
+
* a059392 c6
|
75
|
+
* e917b56 c5
|
76
|
+
* 933c4d1 c4
|
77
|
+
* 4d72f8a c3
|
78
|
+
* 8aab2de c2
|
79
|
+
* 91182df c1
|
80
|
+
"""
|
81
|
+
Then the git log graph matches:
|
82
|
+
"""
|
83
|
+
* a059392 c6
|
84
|
+
* <a_sha> c5
|
85
|
+
* 933c4d1 c4
|
86
|
+
* <a_sha> c3
|
87
|
+
* 8aab2de c2
|
88
|
+
* <a_sha> c1
|
89
|
+
"""
|
90
|
+
Then HEAD starts with a059392
|
91
|
+
* the current branch is "master"
|
92
|
+
* there are <outcome>
|
93
|
+
|
94
|
+
Examples:
|
95
|
+
| repository | outcome |
|
96
|
+
| git repo with unstaged changes | untracked or uncommited changes|
|
97
|
+
| git repo with staged changes | untracked or uncommited changes|
|
98
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Feature: My bootstrapped app kinda works
|
2
|
+
In order to get going on coding my awesome app
|
3
|
+
I want to have aruba and cucumber setup
|
4
|
+
So I don't have to do it myself
|
5
|
+
|
6
|
+
Scenario: App just runs
|
7
|
+
When I get help for "git-loop"
|
8
|
+
Then the exit status should be 0
|
9
|
+
And the banner should document that this app's arguments are:
|
10
|
+
|message|which is not optional|
|
11
|
+
And the output should contain:
|
12
|
+
"""
|
13
|
+
Usage: git loop <from_ref> <to_ref> -m "<message>"
|
14
|
+
"""
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
# Setup :
|
3
|
+
#########
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
# Test :
|
8
|
+
#########
|
9
|
+
|
10
|
+
Then 'the git status is empty' do
|
11
|
+
untracked_or_uncommited_changes.chomp.should be_empty
|
12
|
+
end
|
13
|
+
|
14
|
+
Then 'the git status is:' do |text|
|
15
|
+
untracked_or_uncommited_changes.chomp.should == text
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
Then 'the current branch is "$branch"' do |branch|
|
20
|
+
the_current_branch.should == "* #{branch}"
|
21
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# -----------
|
2
|
+
# Setup :
|
3
|
+
# -----------
|
4
|
+
|
5
|
+
|
6
|
+
# -----------
|
7
|
+
# Tests :
|
8
|
+
# -----------
|
9
|
+
|
10
|
+
|
11
|
+
# Usage :
|
12
|
+
# Then HEAD starts with a059392
|
13
|
+
|
14
|
+
Then 'HEAD starts with $sha_start' do |sha_start|
|
15
|
+
in_current_dir do
|
16
|
+
`git rev-parse HEAD`.should match(/^#{sha_start}/)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
# Usage :
|
22
|
+
# Then the git log graph is:
|
23
|
+
# """
|
24
|
+
# * 4d72f8a c3
|
25
|
+
# * 8aab2de c2
|
26
|
+
# * 91182df c1
|
27
|
+
# """
|
28
|
+
|
29
|
+
Then 'the git log graph is:' do |expected_output|
|
30
|
+
record_time_taken("git-log-graph-IS") do
|
31
|
+
the_git_log_graph.should == expected_output
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# Usage :
|
38
|
+
# Then the git log graph matches:
|
39
|
+
# """
|
40
|
+
# * <a_sha> c3
|
41
|
+
# * 8aab2de c2
|
42
|
+
# * 91182df c1
|
43
|
+
# """
|
44
|
+
|
45
|
+
Then 'the git log graph matches:' do |raw_expected_output|
|
46
|
+
record_time_taken("git-log-graph-matches") do
|
47
|
+
the_git_log_graph.should match(make_smart_regexp(raw_expected_output))
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
|
2
|
+
# Setup :
|
3
|
+
#########
|
4
|
+
|
5
|
+
Given /^I cleared the sandbox directory$/ do
|
6
|
+
in_current_dir do
|
7
|
+
FileUtils.rm_rf("sandbox")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
SANDBOX_PATH = {
|
12
|
+
:default => FIXTURES_PATH + '/sandbox.zip',
|
13
|
+
:staged => FIXTURES_PATH + '/sandbox-with-staged-changes.zip',
|
14
|
+
:unstaged => FIXTURES_PATH + '/sandbox-with-unstaged-changes.zip'
|
15
|
+
}
|
16
|
+
|
17
|
+
Given /^the sandbox is setup$/ do
|
18
|
+
step %Q{I cleared the sandbox directory}
|
19
|
+
step %Q{I unzip the clean git repo in the sandbox}
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
Given /^I unzip the (.*) in the sandbox$/ do |repository|
|
24
|
+
path = case repository
|
25
|
+
when 'clean git repo' then SANDBOX_PATH[:default ]
|
26
|
+
when 'git repo with staged changes' then SANDBOX_PATH[:staged ]
|
27
|
+
when 'git repo with unstaged changes' then SANDBOX_PATH[:unstaged]
|
28
|
+
else raise "NOT SUPPORTED repository : #{repository.inspect}"
|
29
|
+
end
|
30
|
+
record_time_taken("unzip") do
|
31
|
+
in_current_dir do `unzip #{path}` end # 0.5 seconds
|
32
|
+
#step %(I successfully run `unzip #{path}`) # 2.3 seconds
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# Test :
|
38
|
+
#########
|
39
|
+
|
40
|
+
Then /^there are (.*)$/ do |outcome|
|
41
|
+
case outcome
|
42
|
+
when 'no unstaged nor staged changes'
|
43
|
+
untracked_or_uncommited_changes.should be_empty
|
44
|
+
when 'untracked or uncommited changes'
|
45
|
+
untracked_or_uncommited_changes.should_not be_empty
|
46
|
+
else
|
47
|
+
raise "BUG : unsupported outcome #{outcome}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
CLEAN_REPO_LOG =<<TEXT.chomp
|
53
|
+
* edadbb7 c6
|
54
|
+
* da55377 c5 five - fifth commit
|
55
|
+
* 6fddd9c c4 not the third commit
|
56
|
+
* bab916a c3 the third commit
|
57
|
+
* 7355c79 c2 the second commit
|
58
|
+
* 91182df c1
|
59
|
+
TEXT
|
60
|
+
|
61
|
+
Then /^the (?:clean repo|repo) is in its original state$/ do
|
62
|
+
the_git_log_graph.should == CLEAN_REPO_LOG
|
63
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
$timers={}
|
2
|
+
|
3
|
+
|
4
|
+
def record_time_taken(category)
|
5
|
+
category = category.to_sym
|
6
|
+
$timers[category] ||= [0,0]
|
7
|
+
start = Time.now
|
8
|
+
res = yield
|
9
|
+
time, count = $timers[category]
|
10
|
+
$timers[category] = [time + (Time.now - start), count+1]
|
11
|
+
res
|
12
|
+
end
|
13
|
+
|
14
|
+
at_exit do
|
15
|
+
puts "-"*80
|
16
|
+
keylength = $timers.keys.collect(&:length).max
|
17
|
+
puts "**** total || secs/run || runs"
|
18
|
+
puts "-"*66
|
19
|
+
$timers.keys.each do |key|
|
20
|
+
time, count = $timers[key]
|
21
|
+
#puts "**** %{key} : total = %{time} sec. || %{count} secs/run || %{loop_time} runs" % {:key => key, :time => time, :count => count, :loop_time => (time/count)}
|
22
|
+
puts "**** %#{keylength+1}s : %#7.2f sec. || %7.3f sec. || %3d runs" % [key, time, (time/count), count]
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'aruba/cucumber'
|
2
|
+
require 'methadone/cucumber'
|
3
|
+
|
4
|
+
ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
|
5
|
+
LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
|
6
|
+
require LIB_DIR + '/gitloop'
|
7
|
+
|
8
|
+
FIXTURES_PATH = File.join(File.expand_path(File.dirname(__FILE__)),'fixtures')
|
9
|
+
|
10
|
+
Before do
|
11
|
+
# Using "announce" causes massive warnings on 1.9.2
|
12
|
+
@puts = true
|
13
|
+
@original_rubylib = ENV['RUBYLIB']
|
14
|
+
ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
After do
|
18
|
+
ENV['RUBYLIB'] = @original_rubylib
|
19
|
+
FileUtils.rm_rf File.join(File.dirname(__FILE__), '../../tmp/aruba')
|
20
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,21 @@
|
|
1
|
+
def the_current_branch
|
2
|
+
in_current_dir do
|
3
|
+
`git branch | grep "*"`.chomp
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
GIT_LOG_GRAPH_COMMAND = "git log --oneline --graph"
|
9
|
+
|
10
|
+
def the_git_log_graph
|
11
|
+
step %Q{I run `#{GIT_LOG_GRAPH_COMMAND}`} # runs the actual process
|
12
|
+
output_from(GIT_LOG_GRAPH_COMMAND).chomp # only accesses the previous processes outputs
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def untracked_or_uncommited_changes
|
17
|
+
in_current_dir do
|
18
|
+
`git status -s`
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
# turn the feature multiline "enhanced" text into a regexp
|
3
|
+
|
4
|
+
def make_smart_regexp(raw_expected_output)
|
5
|
+
Regexp.compile escape_xxl(raw_expected_output),
|
6
|
+
Regexp::MULTILINE
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
# turn the feature multiline "enhanced" text into a valid regexp-ready string.
|
11
|
+
# It
|
12
|
+
# - escapes the string
|
13
|
+
# - translate <a_sha> -> (\S+)
|
14
|
+
# - replace \n by \s+ because of a problem with multiline strings in Cucumber
|
15
|
+
|
16
|
+
def escape_xxl(raw_text)
|
17
|
+
Regexp.escape(raw_text).tap do |text|
|
18
|
+
text.gsub!('\n', "\\s*") # necessary (? due to Cucumber ??)
|
19
|
+
text.gsub!(/<a_sha>/, '(\S+)')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
data/lib/gitloop.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Gitloop
|
2
|
+
|
3
|
+
module GitUtils
|
4
|
+
|
5
|
+
def current_branch
|
6
|
+
branch = `git branch | grep "*"`
|
7
|
+
branch.empty? ?
|
8
|
+
nil :
|
9
|
+
branch[2..-2] # remove the current branch marker : '* '
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def invalid_commits_sequence?(first_sha, second_sha)
|
14
|
+
output = (first_sha == second_sha) ?
|
15
|
+
call_git("show #{first_sha}") :
|
16
|
+
call_git("show #{first_sha}..#{second_sha}")
|
17
|
+
error = !output.start_with?('commit')
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid_commits_reference?(reference)
|
21
|
+
call_git("show #{reference}").start_with?('commit')
|
22
|
+
rescue Gitloop::GitError
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def shas_for_messages_that_match(from_grep)
|
28
|
+
onelines = (`git log --oneline --grep "#{from_grep}"`).split("\n")
|
29
|
+
shas = onelines.collect { |l| l[/\w+/] }
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require File.dirname(__FILE__) + '/git_utils'
|
3
|
+
|
4
|
+
module Gitloop
|
5
|
+
|
6
|
+
class Looper
|
7
|
+
extend GitUtils
|
8
|
+
|
9
|
+
def self.make_loop_for_references_or_messages(from, to, message=nil)
|
10
|
+
from_ref, to_ref = convert_messages_to_git_refs(from, to)
|
11
|
+
validate_commits_refs_sequence! from_ref, to_ref
|
12
|
+
|
13
|
+
build_the_loop(from_ref, message, to_ref)
|
14
|
+
|
15
|
+
loop_starts_at_very_first_commit = !valid_commits_reference?("#{from_ref}^^")
|
16
|
+
puts loop_starts_at_very_first_commit ?
|
17
|
+
call_git("log --oneline --graph --color --decorate") :
|
18
|
+
call_git("log --oneline --graph --color --decorate #{from_ref}^^..HEAD")
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def self.build_the_loop(from_ref, message, to_ref)
|
24
|
+
curr_branch = current_branch()
|
25
|
+
|
26
|
+
call_git "co #{from_ref}^ --quiet"
|
27
|
+
|
28
|
+
msg = message || "loop #{from_ref}..#{to_ref}"
|
29
|
+
call_git "merge --no-ff #{to_ref} -m \"*** #{msg}\""
|
30
|
+
|
31
|
+
merge_top = call_git "rev-parse HEAD"
|
32
|
+
call_git "co --quiet #{curr_branch}"
|
33
|
+
call_git "rebase #{merge_top}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.convert_messages_to_git_refs(from, to)
|
37
|
+
from_ref = valid_commits_reference?(from) ? from : shas_for_messages_that_match(from).last
|
38
|
+
to_ref = valid_commits_reference?(to ) ? to : shas_for_messages_that_match(to ).last
|
39
|
+
[from_ref, to_ref]
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.call_git(command, error_message_prefix=nil)
|
43
|
+
output, error, status = Open3.capture3("git #{command}")
|
44
|
+
if error.empty?
|
45
|
+
output
|
46
|
+
else
|
47
|
+
raise Gitloop::GitError.new [error_message_prefix,error].compact.join("\n")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.validate_commits_refs_sequence!(first_sha, second_sha)
|
52
|
+
if invalid_commits_sequence?(first_sha, second_sha)
|
53
|
+
error_msg = "#{first_sha}..#{second_sha} is an invalid SHAs sequence"
|
54
|
+
raise Gitloop::GitError.new(error_msg)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gitloop
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alain Ravet
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: methadone
|
16
|
+
requirement: &70328618475780 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70328618475780
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rdoc
|
27
|
+
requirement: &70328618473840 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70328618473840
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: aruba
|
38
|
+
requirement: &70328618471420 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70328618471420
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: &70328618469200 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.9.2
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70328618469200
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: guard-cucumber
|
60
|
+
requirement: &70328618466840 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70328618466840
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: ruby_gntp
|
71
|
+
requirement: &70328618453460 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70328618453460
|
80
|
+
description: easily merge and rebase flat git history
|
81
|
+
email:
|
82
|
+
- alainravet@gmail.com
|
83
|
+
executables:
|
84
|
+
- git-loop
|
85
|
+
- gitloop
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files: []
|
88
|
+
files:
|
89
|
+
- bin/git-loop
|
90
|
+
- bin/gitloop
|
91
|
+
- lib/gitloop/git_utils.rb
|
92
|
+
- lib/gitloop/looper.rb
|
93
|
+
- lib/gitloop/version.rb
|
94
|
+
- lib/gitloop.rb
|
95
|
+
- doc/AFTER.png
|
96
|
+
- doc/BEFORE.png
|
97
|
+
- doc/dev_notes.txt
|
98
|
+
- doc/TODO.txt
|
99
|
+
- LICENSE
|
100
|
+
- README.markdown
|
101
|
+
- Gemfile
|
102
|
+
- Gemfile.lock
|
103
|
+
- features/errors.feature
|
104
|
+
- features/happy_path.feature
|
105
|
+
- features/sandbox.feature
|
106
|
+
- features/shell_command.feature
|
107
|
+
- features/step_definitions/git_steps.rb
|
108
|
+
- features/step_definitions/gitloop_steps.rb
|
109
|
+
- features/step_definitions/sandbox_steps.rb
|
110
|
+
- features/support/benchmarking.rb
|
111
|
+
- features/support/env.rb
|
112
|
+
- features/support/fixtures/sandbox-with-staged-changes.zip
|
113
|
+
- features/support/fixtures/sandbox-with-unstaged-changes.zip
|
114
|
+
- features/support/fixtures/sandbox.zip
|
115
|
+
- features/support/git.rb
|
116
|
+
- features/support/regexp.rb
|
117
|
+
homepage: ''
|
118
|
+
licenses: []
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ! '>='
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
segments:
|
130
|
+
- 0
|
131
|
+
hash: -412658776607306167
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ! '>='
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
segments:
|
139
|
+
- 0
|
140
|
+
hash: -412658776607306167
|
141
|
+
requirements: []
|
142
|
+
rubyforge_project: gitloop
|
143
|
+
rubygems_version: 1.8.15
|
144
|
+
signing_key:
|
145
|
+
specification_version: 3
|
146
|
+
summary: easily merge and rebase flat git history
|
147
|
+
test_files:
|
148
|
+
- features/errors.feature
|
149
|
+
- features/happy_path.feature
|
150
|
+
- features/sandbox.feature
|
151
|
+
- features/shell_command.feature
|
152
|
+
- features/step_definitions/git_steps.rb
|
153
|
+
- features/step_definitions/gitloop_steps.rb
|
154
|
+
- features/step_definitions/sandbox_steps.rb
|
155
|
+
- features/support/benchmarking.rb
|
156
|
+
- features/support/env.rb
|
157
|
+
- features/support/fixtures/sandbox-with-staged-changes.zip
|
158
|
+
- features/support/fixtures/sandbox-with-unstaged-changes.zip
|
159
|
+
- features/support/fixtures/sandbox.zip
|
160
|
+
- features/support/git.rb
|
161
|
+
- features/support/regexp.rb
|