splitpatch.rb 6.5 KB
Newer Older
1
#!/usr/bin/env ruby
2
#
3
#   Copyright
4
#
5 6 7
#       Copyright (C) 2014 Jari Aalto <jari.aalto@cante.net>
#       Copyright (C) 2007-2014 Peter Hutterer <peter.hutterer@who-t.net>
#       Copyright (C) 2007-2014 Benjamin Close <Benjamin.Close@clearchain.com>
8
#
9
#   License
10
#
11 12 13 14
#       This program is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#       the Free Software Foundation; either version 2 of the License, or
#       (at your option) any later version.
15
#
16 17 18 19
#       This program is distributed in the hope that it will be useful,
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#       GNU General Public License for more details.
20
#
21 22 23 24 25
#       You should have received a copy of the GNU General Public License
#       along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#  Description
#
26 27 28 29 30
PROGRAM = "splitpatch"
MYVERSION = 1.0
LICENSE = "GPL-2+"  # See official acronyms: https://spdx.org/licenses/
HOMEPAGE = "https://github.com/jaalto/splitpatch"

31 32 33 34
#       Splitpatch is a simple script to split a patch up into
#       multiple patch files. If the --hunks option is provided on the
#       command line, each hunk gets its own patchfile.

35
class Splitter
36
    def initialize(file)
37
       @filename = file
38 39 40 41 42
       @fullname = false
    end

    def fullname(opt)
       @fullname = opt
43 44 45 46 47 48
    end

    def validFile?
        return File.exist?(@filename) && File.readable?(@filename)
    end

49 50 51 52
    def createFile(filename)
        if File.exists?(filename)
            puts "File #{filename} already exists. Renaming patch."
            appendix = 0
53 54
            zero = appendix.to_s.rjust(3, '0')
            while File.exists?("#{filename}.#{zero}")
55
                appendix += 1
56
                zero = appendix.to_s.rjust(3, '0')
57
            end
58
            filename << ".#{zero}"
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
        end
        return open(filename, "w")
    end

    def getFilename(line)
        tokens = line.split(" ")
        tokens = tokens[1].split(":")
        tokens = tokens[0].split("/")
        if @fullname
            return tokens.join('-')
        else
            return tokens[-1]
        end
    end

74 75
    # Split the patchfile by files 
    def splitByFile
76
        legacy = false
77
        outfile = nil
78
        stream = open(@filename, 'rb')
79 80 81 82
        until (stream.eof?)
            line = stream.readline

            # we need to create a new file
83 84 85 86 87 88 89 90 91 92 93 94
            if (line =~ /^Index: .*/) == 0
                # patch includes Index lines
                # drop into "legacy mode"
                legacy = true
                if (outfile)
                    outfile.close_write
                end
                filename = getFilename(line)
                filename << ".patch"
                outfile = createFile(filename)
                outfile.write(line)
            elsif (line =~ /--- .*/) == 0 and not legacy
95 96 97 98
                if (outfile) 
                    outfile.close_write
                end
                #find filename
99
                filename = getFilename(line)
100
                filename << ".patch"
101
                outfile = createFile(filename)
102 103 104 105 106 107 108 109 110 111
                outfile.write(line)
            else
                if outfile
                    outfile.write(line)
                end
            end
        end
    end

    def splitByHunk
112
        legacy = false
113
        outfile = nil
114
        stream = open(@filename, 'rb')
115 116 117 118 119 120 121
        filename = ""
        counter = 0
        header = ""
        until (stream.eof?)
            line = stream.readline

            # we need to create a new file
122 123 124 125 126 127 128 129 130 131 132 133 134
            if (line =~ /^Index: .*/) == 0
                # patch includes Index lines
                # drop into "legacy mode"
                legacy = true
                filename = getFilename(line)
                header = line
                # remaining 3 lines of header
                for i in 0..2
                    line = stream.readline
                    header << line
                end
                counter = 0
            elsif (line =~ /--- .*/) == 0 and not legacy
135
                #find filename
136
                filename = getFilename(line)
137 138 139 140 141 142 143 144 145
                header = line
                # next line is header too
                line = stream.readline
                header << line
                counter = 0
            elsif (line =~ /@@ .* @@/) == 0
                if (outfile) 
                    outfile.close_write
                end
146 147 148

                zero = counter.to_s.rjust(3, '0')
                hunkfilename = "#{filename}.#{zero}.patch"
149
                outfile = createFile(hunkfilename)
150 151 152 153 154 155 156 157 158 159 160 161 162 163
                counter += 1

                outfile.write(header)
                outfile.write(line)
            else
                if outfile
                    outfile.write(line)
                end
            end
        end
    end

end

164 165 166 167
def help
    puts <<EOF
SYNOPSIS
    #{PROGRAM} [options] FILE.patch
168

169 170 171 172
OPTIONS
    -h,--help
    -H,--hunk
    -V,--version
173

174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
DESCRIPTION

    Split the patch up into files or hunks

    Divide a patch or diff file into pieces. The split can made by file
    or by hunk basis. This makes it possible to separate changes that
    might not be desirable or assemble the patch into a more coherent set
    of changes. See e.g. combinediff(1) from patchutils package.

    Note: only patches in unified format are recognized.

AUTHORS

    Peter Hutterer (orig. Author) <peter.hutterer@who-t.net>
    Benjamin Close (orig. Author) <Benjamin.Close@clearchain.com>
    Jari Aalto (Maintainer) <jari.aalto@cante.net>"

    Homepage: #{HOMEPAGE}
EOF
end

def version
  puts "#{MYVERSION} #{LICENSE} #{HOMEPAGE}"
end

def parsedOptions
    if ARGV.length < 1 or ARGV.length > 2
        puts "ERROR: missing argument. See --help."
        exit 1
    end

    opts = {}

    opt = ARGV[0]
    case opt
    when /^-h$/, /--help/
        opts[:help] = true
    when /^-H$/, /--hunks?/
        opts[:hunk] = true
    when /^-V$/, /--version/
        opts[:version] = true
    when /^-/
        puts "ERROR: Unknown option: #{opt}. See --help."
        exit 1
218
    end
219 220 221 222

    opts[:file] = ARGV[-1]

    return opts
223 224
end

225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
def main
    opts = parsedOptions

    if opts[:help]
        help
	exit
    end

    if opts[:version]
        version
	exit
    end

    s = Splitter.new(opts[:file])
    if !s.validFile?
        puts "File does not exist or is not readable: #{opts[:file]}"
    end

    if opts[:hunk]
        s.splitByHunk
    else
        s.splitByFile
    end
end
249

250
main
251

252
# End of file