public
Description: A port of sake-git to Thor. A set of Thor tasks to make developing with Git easier.
Homepage:
Clone URL: git://github.com/cypher/thor-git.git
thor-git / git.thor
100644 279 lines (244 sloc) 7.167 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
# module: git
 
class Git < Thor
  
  class NoRepositoryError < RuntimeError; end
  class GitError < RuntimeError; end
  class GitRebaseError < GitError; end
  class GitBranchDeleteError < GitError; end
 
 
  desc "open [NAME]", "Create a new branch off master, named NAME"
  def open(name=nil)
    newbranch = name if name && !name.empty?
    newbranch ||= begin
      require "readline"
      print "* Name your branch: "
      Readline.readline.chomp
    end
    branch = git_branch
    if git_branches.include?(newbranch)
      if newbranch == branch
        puts "* Already on branch \"#{newbranch}\""
      else
        puts "* Switching to existing branch \"#{newbranch}\""
        git_checkout(newbranch, :silent => true)
        system "git log --pretty=oneline --abbrev-commit master..HEAD"
      end
      exit(0)
    end
    unless branch == "master"
      puts "* Switching to master"
      git_checkout("master")
    end
    git_checkout(newbranch, :create_branch => true)
    unless $?.exitstatus.zero?
      puts "* Couldn't create branch #{newbranch}, switching back to #{branch}"
      git_checkout(branch)
      exit(1)
    end
    exit(0)
  end
  
  desc "close [NAME]", "Delete the current branch and switch back to master"
  def close(name=nil)
    branch = name if name && !name.empty?
    branch ||= git_branch
    current = git_branch
    if branch == "master"
      $stderr.puts "* Cannot delete master branch"
      exit(1)
    end
    if current == branch
      puts "* Switching to master"
      git_checkout("master")
    end
    puts "* Deleting branch #{branch}"
    git_branch(branch, :delete => true)
    if $?.exitstatus == 1
      $stderr.puts "* Branch #{branch} isn't a strict subset of master, quitting"
      git_checkout(current)
      exit(1)
    end
    git_checkout(current) unless current == branch
    exit(0)
  end
 
  desc "fold", "Merge the current branch into the master branch."
  def fold
    branch = git_branch
    if branch == "master"
      $stderr.puts "* Cannot fold master branch"
      exit(1)
    end
    puts "* Switching to master"
    git_checkout("master")
    puts "* Merging #{branch}"
    @merge_flags ||= {}
    git_merge(branch, @merge_flags)
    if $?.exitstatus == 1
      $stderr.puts "* Merge had errors -- see to your friend"
      exit(1)
    end
    puts "* Switching to #{branch}"
    git_checkout(branch)
  end
 
  desc "ify", "Converts an existing Subversion Repo into a Git Repository"
  method_options :stdlayout => :boolean
  def ify(opts)
    unless File.directory?("./.svn")
      $stderr.puts "This task can only be executed in an existing working copy! (No .svn-Folder found)"
      exit(1)
    end
    svnurl = `svn info`.grep(/^URL:/).first.gsub("URL: ", "").chomp
 
    # Remove "trunk" from the svnurl if we use the stdlayout option
    svnurl.slice!(-5, 5) if opts[:stdlayout] && svnurl =~ /trunk$/
 
    project = "../#{File.basename(Dir.pwd)}.git"
    puts(cmd = "git svn clone #{"--stdlayout" if opts[:stdlayout]} #{svnurl} #{project}")
    `#{cmd}`
  end
 
  desc "push", "Push local commits into the remote repository"
  def push
    git_stash do
      puts "* Pushing changes..."
      git_push
      branch = git_branch
      if branch != "master"
        git_checkout("master")
        puts "* Porting changes into master"
        git_rebase
        git_checkout(branch)
      end
    end
  end
 
  desc "squash", "Squash the current branch into the master branch."
  def squash
    @merge_flags = {:squash => true}
    fold
  end
 
  desc "update", "Pull new commits from the repository"
  def update
    git_stash do
      branch = git_branch
      if branch == "master"
        switch = false
      else
        switch = true
        git_checkout("master")
        puts "* Switching back to master..."
      end
      puts "* Pulling in new commits..."
      git_fetch
      git_rebase
      if switch
        puts "* Porting changes into #{branch}..."
        git_checkout(branch)
        git_rebase(branch)
      end
    end
  end
 
  desc "all", "Update all branches"
  def all
    git_stash do
      branch = git_branch
      switch = true
      git_branches.each do |b|
        puts "* Updating branch #{b}"
        begin
          git_rebase(b)
        rescue GitRebaseError => e
          puts "* Couldn't rebase #{b}, aborting so you can clean it up"
          switch = false
          break
        end
      end
      git_checkout(branch) if switch
    end
  end
 
  private
  
  def git_branch(what = nil, opts = {})
    # If no name is given, return the name of the current branch
    return `git branch`.grep(/^\*/).first.strip[(2..-1)] if what.nil?
    
    delete = opts[:delete] ? "-d" : ""
    force_delete = opts[:force_delete] ? "-D" : ""
    
    `git branch #{delete} #{force_delete} #{what}`
    assert_branch_delete_succeeded(what)
  end
  
  def git_branches
    `git branch`.to_a.map { |b| b[(2..-1)].chomp }
  end
  
  def git_merge(what, opts = {})
    squash = opts[:squash] ? "--squash" : ""
    
    `git merge #{squash} #{what}`
  end
  
  def git?
    `git status`
    $?.exitstatus != 128
  end
  
  def git_stash
    `git diff-files --quiet --ignore-submodules`
    if $?.exitstatus == 1
      stash = true
      clear = (`git stash list`.scan("\n").size == 0)
      puts "* Saving changes..."
      `git stash save`
    else
      stash = false
    end
    begin
      yield
    rescue => e
      puts "* Encountered an error (#{e}), backing out..."
    ensure
      if stash
        puts "* Applying changes..."
        `git stash apply`
        `git stash clear` if clear
      end
    end
  end
 
  def git_checkout(what = nil, opts = {})
    silent = opts[:silent] ? "-q" : ""
    create_branch = opts[:create_branch] ? "-b" : ""
    
    branch = git_branch
    `git checkout #{silent} #{create_branch} #{what}` if branch != what
    if block_given?
      yield
      `git checkout #{silent} #{create_branch} #{branch}` if branch != what
    end
  end
 
  def git_fetch
    `git#{" svn" if git_svn?} fetch`
  end
 
  def assert_command_succeeded(*args)
    raise(*args) unless $?.exitstatus == 0
  end
 
  def assert_rebase_succeeded(what = nil)
    assert_command_succeeded(GitRebaseError, "conflict while rebasing branch #{what}")
  end
  
  def assert_branch_delete_succeeded(what = nil)
    assert_command_succeeded(GitBranchDeleteError, "branch #{what} could not be deleted")
  end
  
  def git_rebase(what = nil)
    if git_svn?
      git_checkout(what) do
        `git svn rebase --local`
        assert_rebase_succeeded(what)
      end
    else
      `git rebase origin/master #{what}`
      assert_rebase_succeeded(what)
    end
  end
  
  def git_push
    git_svn? ? (`git svn dcommit`) : (`git push`)
  end
  
  def chroot
    # store cwd
    dir = Dir.pwd
    
    # find .git
    until File.directory?('.git') || File.expand_path('.') == '/'
      Dir.chdir('..')
    end
    is_git = File.directory?('.git')
    
    raise NoRepositoryError, "No repository found containing #{dir}" unless is_git
  end
  
  def git_svn?
    chroot
    (not File.readlines(".git/config").grep(/^\[svn-remote "svn"\]\s*$/).empty?)
  end
end