Take the 2008 Git User's Survey and help out! [ hide ]

public
Rubygem
Description: Simple tool to help track git and svn vendor branches in a git repository
Homepage: http://evil.che.lu/projects/braid
Clone URL: git://github.com/evilchelu/braid.git
Search Repo:
evilchelu (author)
Thu May 01 05:30:49 -0700 2008
commit  acbeb9a153e6596de9c5080876d092761c39b68f
tree    d4247829e8505c1cdedf5ac490244786e2945852
parent  ff54b7afe1348388e35e021d4dbe0043fecaec40
braid / lib / braid / operations.rb
100644 297 lines (250 sloc) 8.18 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
module Braid
  module Operations
    module Git
      def git_commit(message)
        status, out, err = exec("git commit -m #{message.inspect} --no-verify")
 
        if status == 0
          true
        elsif out.match("nothing to commit")
          false
        else
          raise Braid::Commands::ShellExecutionError, err
        end
      end
 
      def git_fetch(remote)
        # open4 messes with the pipes of index-pack
        system("git fetch -n #{remote} &> /dev/null")
        raise Braid::Commands::ShellExecutionError unless $? == 0
        true
      end
 
      def git_checkout(treeish)
        # TODO debug
        msg "Checking out '#{treeish}'."
        exec!("git checkout #{treeish}")
        true
      end
 
      # Returns the base commit or nil.
      def git_merge_base(target, source)
        status, out, err = exec!("git merge-base #{target} #{source}")
        out.strip
      rescue Braid::Commands::ShellExecutionError
        nil
      end
 
      def git_rev_parse(commit)
        status, out, err = exec!("git rev-parse #{commit}")
        out.strip
      end
 
      # Implies tracking.
      def git_remote_add(remote, path, branch)
        exec!("git remote add -t #{branch} -m #{branch} #{remote} #{path}")
        true
      end
 
      def git_reset_hard(target)
        exec!("git reset --hard #{target}")
        true
      end
 
      # Implies no commit.
      def git_merge_ours(commit)
        exec!("git merge -s ours --no-commit #{commit}")
        true
      end
 
      # Implies no commit.
      def git_merge_subtree(commit)
        # TODO which options are needed?
        exec!("git merge -s subtree --no-commit --no-ff #{commit}")
        true
      end
 
      def git_read_tree(treeish, prefix)
        exec!("git read-tree --prefix=#{prefix}/ -u #{treeish}")
        true
      end
 
      def git_rm_r(path)
        exec!("git rm -r #{path}")
        true
      end
 
      def local_changes?
        status, out, err = exec("git status")
        out.split("\n").grep(/nothing to commit \(working directory clean\)/).empty?
      end
    end
 
    module Svn
      # FIXME move
      def svn_remote_head_revision(path)
        # not using svn info because it's retarded and doesn't show the actual last changed rev for the url
        # git svn has no clue on how to get the actual HEAD revision number on it's own
        status, out, err = exec!("svn log -q --limit 1 #{path}")
        out.split(/\n/).find { |x| x.match /^r\d+/ }.split(" | ")[0][1..-1].to_i
      end
 
      # FIXME move
      def svn_git_commit_hash(remote, revision)
        status, out, err = exec!("git svn log --show-commit --oneline -r #{revision} #{remote}")
        part = out.split(" | ")[1]
        raise Braid::Svn::UnknownRevision, "unknown revision: #{revision}" unless part
        invoke(:git_rev_parse, part)
      end
 
      def git_svn_fetch(remote)
        # open4 messes with the pipes of index-pack
        system("git svn fetch #{remote} &> /dev/null")
        true
      end
 
      def git_svn_init(remote, path)
        exec!("git svn init -R #{remote} --id=#{remote} #{path}")
        true
      end
    end
 
    module Helpers
      [:invoke, :exec, :exec!].each do |method|
        define_method(method) do |*args|
          Braid::Operations.send(method, *args)
        end
      end
 
      def extract_git_version
        status, out, err = exec!("git --version")
        return out.sub(/^git version/, "").strip
      end
 
      def verify_git_version(required)
        required_version = required.split(".")
        actual_version = extract_git_version.split(".")
        actual_version.each_with_index do |actual_piece, idx|
          required_piece = required_version[idx]
 
          return true unless required_piece
 
          case (actual_piece <=> required_piece)
            when -1
              return false
            when 1
              return true
            when 0
              next
          end
        end
 
        return actual_version.length >= required_version.length
      end
 
      def find_git_revision(commit)
        invoke(:git_rev_parse, commit)
      rescue Braid::Commands::ShellExecutionError
        raise Braid::Git::UnknownRevision, "unknown revision: #{commit}"
      end
 
      def clean_svn_revision(revision)
        if revision
          revision.to_i
        else
          nil
        end
      end
 
      def validate_svn_revision(old_revision, new_revision, path)
        return unless new_revision = clean_svn_revision(new_revision)
        old_revision = clean_svn_revision(old_revision)
 
        # TODO add checks for unlocked mirrors
        if old_revision
          if new_revision < old_revision
            raise Braid::Commands::LocalRevisionIsHigherThanRequestedRevision
          elsif new_revision == old_revision
            raise Braid::Commands::MirrorAlreadyUpToDate
          end
        end
 
        if path && invoke(:svn_remote_head_revision, path) < new_revision
          raise Braid::Commands::RequestedRevisionIsHigherThanRemoteRevision
        end
 
        true
      end
 
      # Make sure the revision is valid, then clean it.
      def validate_revision_option(params, options)
        if options["revision"]
          case params["type"]
          when "git"
            options["revision"] = find_git_revision(options["revision"])
          when "svn"
            validate_svn_revision(params["revision"], options["revision"], params["remote"])
            options["revision"] = clean_svn_revision(options["revision"])
          end
        end
 
        true
      end
 
      def determine_target_commit(params, options)
        if options["revision"]
          if params["type"] == "svn"
            invoke(:svn_git_commit_hash, params["local_branch"], options["revision"])
          else
            invoke(:git_rev_parse, options["revision"])
          end
        else
          invoke(:git_rev_parse, params["local_branch"])
        end
      end
 
      def display_revision(type, revision)
        type == "svn" ? "r#{revision}" : "'#{revision[0, 7]}'"
      end
    end
 
    module Mirror
      def get_current_branch
        status, out, err = exec!("git branch | grep '*'")
        out[2..-1]
      end
 
      def create_work_branch
        # check if branch exists
        status, out, err = exec("git branch | grep '#{WORK_BRANCH}'")
        if status != 0
          # then create it
          msg "Creating work branch '#{WORK_BRANCH}'."
          exec!("git branch #{WORK_BRANCH}")
        end
 
        true
      end
 
      def get_work_head
        find_git_revision(WORK_BRANCH)
      end
 
      def add_config_file
        exec!("git add #{CONFIG_FILE}")
        true
      end
 
      def check_merge_status(commit)
        commit = find_git_revision(commit)
        # tip from spearce in #git:
        # `test z$(git merge-base A B) = z$(git rev-parse --verify A)`
        if invoke(:git_merge_base, commit, "HEAD") == commit
          raise Braid::Commands::MirrorAlreadyUpToDate
        end
 
        true
      end
 
      def fetch_remote(type, remote)
        msg "Fetching data from '#{remote}'."
        case type
        when "git"
          invoke(:git_fetch, remote)
        when "svn"
          invoke(:git_svn_fetch, remote)
        end
      end
 
      def find_remote(remote)
        # TODO clean up and maybe return more information
        !!File.readlines(".git/config").find { |line| line =~ /^\[(svn-)?remote "#{remote}"\]/ }
      end
    end
 
    extend Git
    extend Svn
 
    def self.invoke(*args)
      send(*args)
    end
 
    def self.exec(cmd)
      #puts cmd
      out = ""
      err = ""
      cmd.strip!
 
      ENV['LANG'] = 'C' unless ENV['LANG'] == 'C'
      status = Open4::popen4(cmd) do |pid, stdin, stdout, stderr|
        out = stdout.read.strip
        err = stderr.read.strip
      end
      [status.exitstatus, out, err]
    end
 
    def self.exec!(cmd)
      status, out, err = exec(cmd)
      raise Braid::Commands::ShellExecutionError, err unless status == 0
      return status, out, err
    end
 
    private
      def self.msg(str)
        Braid::Command.msg(str)
      end
  end
end