git-patch-patch 0.0.1

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.
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
+
5
+ require 'git-patch-patch/patch_patcher'
@@ -0,0 +1,225 @@
1
+ # encoding: utf-8
2
+
3
+ # from stdlib
4
+ require 'pathname'
5
+ require 'forwardable'
6
+
7
+ # from me
8
+ require 'git-trifle'
9
+ require 'path-accessor'
10
+
11
+ # this class knows how make a diff and
12
+ # write it to a retrievable patch
13
+ # how to apply a patch and commit it
14
+ module Git
15
+
16
+ class Trifle
17
+
18
+ # not too bright though
19
+ class PatchPatcher
20
+
21
+ # clean API best bud
22
+ extend Forwardable
23
+
24
+ # what we'll use from git-trifle
25
+ def_delegators :@t, :commits, :clone, :checkout
26
+
27
+ # current patch generated by the last diff performed
28
+ attr_reader :patch
29
+
30
+ # open the repo in path
31
+ # set the root path from where the patches are retrieved
32
+ def initialize(options)
33
+ options = {branch: 'master', patch_dir: '/tmp'}.merge options
34
+
35
+ # git handler
36
+ @t = Git::Trifle.new options[:repo]
37
+ # where the patch file will be stored
38
+ @patch_dir = Pathstring.new options[:patch_dir]
39
+ # branch from which the commits will be reviewed
40
+ @branch = options[:branch]
41
+ # list of reviewed commits
42
+ @patcher_commits = commits branch: @branch
43
+ end
44
+
45
+ def patch_work(pattern, replacement, *options, &block)
46
+ # work branch
47
+ checkout_work_branch
48
+
49
+ # main workflow
50
+ @patcher_commits.each_with_index do |c, id|
51
+ # exit on the last commit
52
+ break unless c_next = @patcher_commits[id + 1]
53
+
54
+ # current diff sets the current patch
55
+ diff c, c_next
56
+
57
+ # even if work is done we yield the patch
58
+ # to allow modification
59
+ patch.work == :done &&
60
+ yield(patch)
61
+
62
+ # if a patch file is found, we commit and
63
+ # jump to next iteration
64
+ patch.file.exist? &&
65
+ commit_patch(&block) &&
66
+ next
67
+
68
+ # patch filenames and / or patch content
69
+ options.each do |work|
70
+ patch.send "patch_#{work}", pattern, replacement
71
+ yield patch
72
+ end
73
+
74
+ # save to file and commit
75
+ patch.save
76
+ commit_patch &block
77
+ end
78
+ end
79
+
80
+ # this class base operation
81
+ # a diff between two sha's stored in @patch
82
+ def diff(first_commit, second_commit)
83
+ @patch = PatchPatch.new @t.diff(first_commit, second_commit),
84
+ first_commit: first_commit,
85
+ second_commit: second_commit,
86
+ file: patch_file_for(second_commit),
87
+ work: patch_file_for(second_commit).exist?
88
+ end
89
+
90
+ # plain as plain :
91
+ # - patch from file
92
+ # - apply and commit with a reuse of the commit message
93
+ # from the second sha of the diff which generated the patch
94
+ def commit_patch
95
+ patch.error = nil
96
+
97
+ @t.apply patch.file
98
+ @t.add '.'
99
+ @t.commit '', reuse_message: patch.second_commit
100
+ rescue => error
101
+ # Houston we have a problem
102
+ patch.error = error
103
+ # first we yield to allow a fix
104
+ yield patch if block_given?
105
+
106
+ # if a fix was made, retry
107
+ if patch.changed?
108
+ patch.save
109
+ retry
110
+ end
111
+ end
112
+
113
+ def checkout_work_branch
114
+ # checkout -b barbaric_name sha
115
+ checkout "__patch_patcher_#{Time.now.to_f}", commit: @patcher_commits.first
116
+ end
117
+
118
+ # unique patch filename for a given sha and a given local repo'
119
+ def patch_file_for(commit)
120
+ @patch_dir.join 'git-patch-patch',
121
+ # horrendous but necessary to ensure unicity
122
+ # of path, without having an absolute path
123
+ # that 'join' doesn't like
124
+ Pathname(@t.directory).realpath.to_s.sub('/',''),
125
+ commit.to_s,
126
+ 'patch'
127
+ end
128
+
129
+ end
130
+
131
+ # patch model as a String for its primary
132
+ # interface, but it knows a lot more
133
+ class PatchPatch < String
134
+
135
+ # file handling
136
+ extend PathAccessor
137
+
138
+ path_accessor :file
139
+ attr_accessor :first_commit, :second_commit, :work, :error
140
+
141
+ # quick list of Strings methods that perform in-place
142
+ # modification
143
+ # we install a simple shunt on these methods to implement
144
+ # the changed? method
145
+
146
+ # :!= is left here by design to have changed? answer true
147
+ # the first time it is called after a possible change
148
+ instance_methods.grep(/\!/).push(:<<).each do |meth|
149
+ define_method meth do |*args, &block|
150
+ # record previous state
151
+ @previous = self.to_s
152
+ # call to vanilla
153
+ super *args, &block
154
+ end
155
+ end
156
+
157
+ # defines patch_patch and patch_filenames
158
+ %w|filenames patch|.each do |work|
159
+ define_method "patch_#{work}".to_sym do |pattern, replacement|
160
+ # the actual job on patch
161
+ send work.to_sym, pattern, replacement
162
+
163
+ # changed to avoid messing with changed? the user
164
+ # could call
165
+ changed = self.to_s != @previous.to_s
166
+ @work = changed ? work.to_sym : :nothing
167
+ changed # return value says if it did anything
168
+ end
169
+ end
170
+
171
+ def initialize(string, attributes=nil)
172
+ # instance attributes in one line
173
+ (attributes ||= {}).each { |k, v| send "#{k}=", v }
174
+
175
+ # job done ? ok then...
176
+ if @work == true || @work == :done
177
+ # give work the value we want
178
+ @work = :done
179
+ string = file.read
180
+ end
181
+
182
+ # changes handler
183
+ @previous = string.to_s
184
+
185
+ # vanilla
186
+ super string
187
+ end
188
+
189
+ def changed?
190
+ self != @previous
191
+ end
192
+
193
+ # to file
194
+ def save
195
+ # trailing \n or git complains
196
+ file.save!(self + "\n")
197
+ end
198
+
199
+ # patch content from file
200
+ def reload_from_file
201
+ @previous = self.to_s
202
+ replace file.read
203
+ end
204
+
205
+ private
206
+
207
+ # naughty nasty operation on filenames in patch
208
+ def filenames(pattern, replacement)
209
+ # we trap what begins with a:/ or b/ until we find the pattern
210
+ # it's replaced by what's captured with replacement appended
211
+ gsub! /(a|b)\/([^\n]*?)#{pattern}/, "\\1/\\2#{replacement}"
212
+ end
213
+
214
+ # Now this not pretty : we allow ourselves to alter anything
215
+ # in the patch according to pattern/replacement couple.
216
+ # Welcome conflictorama
217
+ def patch(pattern, replacement)
218
+ gsub! pattern, replacement
219
+ end
220
+
221
+ end
222
+
223
+ end
224
+
225
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ module Git
4
+ class Trifle
5
+ class PatchPatcher
6
+ VERSION = "0.0.1"
7
+ end
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git-patch-patch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - lacravate
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: getopt
16
+ requirement: !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: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: git-trifle
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: path-accessor
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: A script to rewrite git patches/commits, while keeping commits history
79
+ email:
80
+ - lacravate@lacravate.fr
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - lib/git-patch-patch.rb
86
+ - lib/git-patch-patch/patch_patcher.rb
87
+ - lib/git-patch-patch/version.rb
88
+ homepage: https://github.com/lacravate/git-patch-patch
89
+ licenses: []
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project: ! '[none]'
108
+ rubygems_version: 1.8.24
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: A script to rewrite git patches/commits, while keeping commits history
112
+ test_files: []