git-improved 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGES.md +8 -0
- data/MIT-LICENSE +21 -0
- data/README.md +307 -0
- data/bin/gi +7 -0
- data/git-improved.gemspec +34 -0
- data/lib/git-improved.rb +1626 -0
- data/test/action_test.rb +1953 -0
- data/test/shared.rb +96 -0
- metadata +98 -0
data/lib/git-improved.rb
ADDED
@@ -0,0 +1,1626 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
###
|
6
|
+
### $Release: 0.1.0 $
|
7
|
+
### $Copyright: copyright(c) 2023 kwatch@gmail.com $
|
8
|
+
### $License: MIT License $
|
9
|
+
###
|
10
|
+
|
11
|
+
require 'benry/cmdapp'
|
12
|
+
#require 'benry/unixcommand' # lazy load
|
13
|
+
|
14
|
+
|
15
|
+
if (RUBY_VERSION.split('.').collect(&:to_i) <=> [2, 6]) < 0
|
16
|
+
Kernel.module_eval do
|
17
|
+
alias __orig_system system
|
18
|
+
def system(*args, **kws)
|
19
|
+
if kws.delete(:exception)
|
20
|
+
__orig_system(*args, **kws) or
|
21
|
+
raise "Command failed: #{args.join(' ')}"
|
22
|
+
else
|
23
|
+
__orig_system(*args, **kws)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
module GitImproved
|
31
|
+
|
32
|
+
VERSION = "$Version: 0.0.0 $".split()[1]
|
33
|
+
ENVVAR_INITFILE = "GI_INITFILE"
|
34
|
+
|
35
|
+
|
36
|
+
class GitConfig
|
37
|
+
|
38
|
+
def initialize()
|
39
|
+
@prompt = "[#{File.basename($0)}]$ "
|
40
|
+
@default_action = "status:here" # or: "status:info"
|
41
|
+
@initial_branch = "main" # != 'master'
|
42
|
+
@initial_commit_message = "Initial commit (empty)"
|
43
|
+
@gitignore_items = ["*~", "*.DS_Store", "tmp", "*.pyc"]
|
44
|
+
@history_graph_format = "%C(auto)%h %ad <%al> | %d %s"
|
45
|
+
#@history_graph_format = "\e[32m%h %ad\e[0m <%al> \e[2m|\e[0m\e[33m%d\e[0m %s"
|
46
|
+
@history_graph_options = ["--graph", "--date=short", "--decorate"]
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_accessor :prompt
|
50
|
+
attr_accessor :default_action
|
51
|
+
attr_accessor :initial_branch
|
52
|
+
attr_accessor :initial_commit_message
|
53
|
+
attr_accessor :gitignore_items
|
54
|
+
attr_accessor :history_graph_format
|
55
|
+
#attr_accessor :history_graph_format
|
56
|
+
attr_accessor :history_graph_options
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
GIT_CONFIG = GitConfig.new
|
62
|
+
|
63
|
+
APP_CONFIG = Benry::CmdApp::Config.new("Git Improved", VERSION).tap do |c|
|
64
|
+
c.option_topic = true
|
65
|
+
c.option_quiet = true
|
66
|
+
c.option_color = true
|
67
|
+
c.option_dryrun = true
|
68
|
+
c.format_option = " %-19s : %s"
|
69
|
+
c.format_action = " %-19s : %s"
|
70
|
+
c.backtrace_ignore_rexp = /\/gi(?:t-improved\.rb)?:/
|
71
|
+
c.help_postamble = {
|
72
|
+
"Example:" => <<END,
|
73
|
+
$ mkdir mysample # or: gi repo:clone github:<user>/<repo>
|
74
|
+
$ cd mysample
|
75
|
+
$ gi repo:init -u yourname -e yourname@gmail.com
|
76
|
+
$ vi README.md # create a new file
|
77
|
+
$ gi track README.md # track files into the repository
|
78
|
+
$ gi cc "add README file" # commit changes
|
79
|
+
$ vi README.md # update an existing file
|
80
|
+
$ gi stage . # add changes into staging area
|
81
|
+
$ gi staged # show changes in staging area
|
82
|
+
$ gi cc "update README file" # commit changes
|
83
|
+
$ gi repo:remote:origin github:yourname/mysample # set remote repo
|
84
|
+
$ gi push # upload local commits to remote repo
|
85
|
+
END
|
86
|
+
"Document:" => " https://kwatch.github.io/git-improved/",
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
class GitCommandFailed < Benry::CmdApp::CommandError
|
92
|
+
|
93
|
+
def initialize(git_command=nil)
|
94
|
+
super "Git command failed: #{git_command}"
|
95
|
+
@git_command = git_command
|
96
|
+
end
|
97
|
+
|
98
|
+
attr_reader :git_commit
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
module ActionHelper
|
104
|
+
|
105
|
+
def git(*args)
|
106
|
+
argstr = args.collect {|s| _qq(s) }.join(" ")
|
107
|
+
echoback("git #{argstr}")
|
108
|
+
return if $DRYRUN_MODE
|
109
|
+
out = $SUBPROCESS_OUTPUT || nil
|
110
|
+
if out
|
111
|
+
system(["git", "git"], *args, out: out, err: out) or
|
112
|
+
raise GitCommandFailed, "git #{argstr}"
|
113
|
+
else
|
114
|
+
system(["git", "git"], *args) or
|
115
|
+
raise GitCommandFailed, "git #{argstr}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def git!(*args)
|
120
|
+
git(*args)
|
121
|
+
rescue GitCommandFailed
|
122
|
+
false
|
123
|
+
end
|
124
|
+
|
125
|
+
def system!(command)
|
126
|
+
out = $SUBPROCESS_OUTPUT || nil
|
127
|
+
if out
|
128
|
+
system command, exception: true, out: out, err: out
|
129
|
+
else
|
130
|
+
system command, exception: true
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
protected
|
135
|
+
|
136
|
+
def curr_branch()
|
137
|
+
return `git rev-parse --abbrev-ref HEAD`.strip()
|
138
|
+
end
|
139
|
+
|
140
|
+
def prev_branch()
|
141
|
+
#s = `git rev-parse --symbolic-full-name @{-1}`.strip()
|
142
|
+
#return s.split("/").last
|
143
|
+
return `git rev-parse --abbrev-ref @{-1}`.strip()
|
144
|
+
end
|
145
|
+
|
146
|
+
def parent_branch()
|
147
|
+
# ref: https://stackoverflow.com/questions/3161204/
|
148
|
+
# git show-branch -a \
|
149
|
+
# | sed 's/].*//' \
|
150
|
+
# | grep '\*' \
|
151
|
+
# | grep -v "\\[$(git branch --show-current)\$" \
|
152
|
+
# | head -n1 \
|
153
|
+
# | sed 's/^.*\[//'
|
154
|
+
curr = curr_branch()
|
155
|
+
end_str = "[#{curr}\n"
|
156
|
+
output = `git show-branch -a`
|
157
|
+
output.each_line do |line|
|
158
|
+
line = line.sub(/\].*/, '')
|
159
|
+
next unless line =~ /\*/
|
160
|
+
next if line.end_with?(end_str)
|
161
|
+
parent = line.sub(/^.*?\[/, '').strip()
|
162
|
+
return parent
|
163
|
+
end
|
164
|
+
return nil
|
165
|
+
end
|
166
|
+
|
167
|
+
def resolve_branch(branch)
|
168
|
+
case branch
|
169
|
+
when "CURR" ; return curr_branch()
|
170
|
+
when "PREV" ; return prev_branch()
|
171
|
+
when "PARENT" ; return parent_branch()
|
172
|
+
when "-" ; return prev_branch()
|
173
|
+
else ; return branch
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def resolve_except_prev_branch(branch)
|
178
|
+
if branch == nil || branch == "-" || branch == "PREV"
|
179
|
+
return "-"
|
180
|
+
else
|
181
|
+
return resolve_branch(branch)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def resolve_repository_url(url)
|
186
|
+
case url
|
187
|
+
when /^github:/
|
188
|
+
url =~ /^github:(?:\/\/)?([^\/]+)\/([^\/]+)$/ or
|
189
|
+
raise action_error("Invalid GitHub URL: #{url}")
|
190
|
+
user = $1; project = $2
|
191
|
+
return "git@github.com:#{user}/#{project}.git"
|
192
|
+
when /^gitlab:/
|
193
|
+
url =~ /^gitlab:(?:\/\/)?([^\/]+)\/([^\/]+)$/ or
|
194
|
+
raise action_error("Invalid GitLub URL: #{url}")
|
195
|
+
user = $1; project = $2
|
196
|
+
return "git@gitlab.com:#{user}/#{project}.git"
|
197
|
+
else
|
198
|
+
return url
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def remote_repo_of_branch(branch)
|
203
|
+
branch_ = Regexp.escape(branch)
|
204
|
+
output = `git config --get-regexp '^branch\\.#{branch_}\\.remote'`
|
205
|
+
arr = output.each_line.grep(/^branch\..*?\.remote (.*)/) { $1 }
|
206
|
+
remote = arr.empty? ? nil : arr[0]
|
207
|
+
return remote
|
208
|
+
end
|
209
|
+
|
210
|
+
def color_mode?
|
211
|
+
return $stdout.tty?
|
212
|
+
end
|
213
|
+
|
214
|
+
def ask_to_user(question)
|
215
|
+
print "#{question} "
|
216
|
+
$stdout.flush()
|
217
|
+
answer = $stdin.readline().strip()
|
218
|
+
return answer.empty? ? nil : answer
|
219
|
+
end
|
220
|
+
|
221
|
+
def ask_to_user!(question)
|
222
|
+
answer = ""
|
223
|
+
while answer.empty?
|
224
|
+
print "#{question}: "
|
225
|
+
$stdout.flush()
|
226
|
+
answer = $stdin.read().strip()
|
227
|
+
end
|
228
|
+
return answer
|
229
|
+
end
|
230
|
+
|
231
|
+
def confirm(question, default_yes: true)
|
232
|
+
if default_yes
|
233
|
+
return _confirm(question, "[Y/n]", "Y") {|ans| ans !~ /\A[nN]/ }
|
234
|
+
else
|
235
|
+
return _confirm(question, "[y/N]", "N") {|ans| ans !~ /\A[yY]/ }
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
def _confirm(question, prompt, default_answer, &block)
|
242
|
+
print "#{question} #{prompt}: "
|
243
|
+
$stdout.flush()
|
244
|
+
answer = $stdin.readline().strip()
|
245
|
+
anser = default_answer if answer.empty?
|
246
|
+
return yield(answer)
|
247
|
+
end
|
248
|
+
|
249
|
+
def _qq(str, force: false)
|
250
|
+
if force || str =~ /\A[-+\w.,:=%\/^@]+\z/
|
251
|
+
return str
|
252
|
+
elsif str =~ /\A(-[-\w]+=)/
|
253
|
+
return $1 + _qq($')
|
254
|
+
else
|
255
|
+
#return '"' + str.gsub(/[$!`\\"]/) { "\\#{$&}" } + '"'
|
256
|
+
return '"' + str.gsub(/[$!`\\"]/, "\\\\\\&") + '"'
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def _same_commit_id?(branch1, branch2)
|
261
|
+
arr = `git rev-parse #{branch1} #{branch2}`.split()
|
262
|
+
return arr[0] == arr[1]
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
|
267
|
+
|
268
|
+
class GitAction < Benry::CmdApp::Action
|
269
|
+
#include Benry::UnixCommand ## include lazily
|
270
|
+
include ActionHelper
|
271
|
+
|
272
|
+
protected
|
273
|
+
|
274
|
+
def prompt()
|
275
|
+
return "[gi]$ "
|
276
|
+
end
|
277
|
+
|
278
|
+
def echoback(command)
|
279
|
+
e1, e2 = color_mode?() ? ["\e[2m", "\e[0m"] : ["", ""]
|
280
|
+
puts "#{e1}#{prompt()}#{command}#{e2}" unless $QUIET_MODE
|
281
|
+
#puts "#{e1}#{super}#{e2}" unless $QUIET_MODE
|
282
|
+
end
|
283
|
+
|
284
|
+
def _lazyload_unixcommand()
|
285
|
+
require 'benry/unixcommand'
|
286
|
+
GitAction.class_eval {
|
287
|
+
include Benry::UnixCommand
|
288
|
+
remove_method :mkdir, :cd, :touch
|
289
|
+
}
|
290
|
+
end
|
291
|
+
private :_lazyload_unixcommand
|
292
|
+
|
293
|
+
def sys(*args)
|
294
|
+
if $DRYRUN_MODE
|
295
|
+
echoback args.join(' ')
|
296
|
+
else
|
297
|
+
_lazyload_unixcommand()
|
298
|
+
super
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def sys!(*args)
|
303
|
+
if $DRYRUN_MODE
|
304
|
+
echoback args.join(' ')
|
305
|
+
else
|
306
|
+
_lazyload_unixcommand()
|
307
|
+
super
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def mkdir(*args)
|
312
|
+
if $DRYRUN_MODE
|
313
|
+
echoback "mkdir #{args.join(' ')}"
|
314
|
+
else
|
315
|
+
_lazyload_unixcommand()
|
316
|
+
super
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def cd(dir, &block)
|
321
|
+
if $DRYRUN_MODE
|
322
|
+
echoback "cd #{dir}"
|
323
|
+
if File.directory?(dir)
|
324
|
+
Dir.chdir dir, &block
|
325
|
+
else
|
326
|
+
yield if block_given?()
|
327
|
+
end
|
328
|
+
echoback "cd -" if block_given?()
|
329
|
+
else
|
330
|
+
_lazyload_unixcommand()
|
331
|
+
super
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def touch(*args)
|
336
|
+
if $DRYRUN_MODE
|
337
|
+
echoback "touch #{args.join(' ')}"
|
338
|
+
else
|
339
|
+
_lazyload_unixcommand()
|
340
|
+
super
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
public
|
345
|
+
|
346
|
+
|
347
|
+
##
|
348
|
+
## status:
|
349
|
+
##
|
350
|
+
category "status:" do
|
351
|
+
|
352
|
+
@action.("same as 'stats:compact .'", important: true)
|
353
|
+
def here()
|
354
|
+
git "status", "-sb", "."
|
355
|
+
end
|
356
|
+
|
357
|
+
@action.("show various infomation of current status")
|
358
|
+
def info(path=".")
|
359
|
+
#command = "git status -sb #{path} | awk '/^\\?\\? /{print $2}' | sed 's!/$!!' | xargs ls -dF --color"
|
360
|
+
command = "git status -sb #{path} | sed -n 's!/$!!;/^??/s/^?? //p' | xargs ls -dF --color"
|
361
|
+
#command = "git status -sb #{path} | perl -ne 'print if s!^\\?\\? (.*?)/?$!\\1!' | xargs ls -dF --color"
|
362
|
+
#command = "git status -sb #{path} | ruby -ne \"puts \\$1 if /^\\?\\? (.*?)\\/?$/\" | xargs ls -dF --color"
|
363
|
+
echoback command
|
364
|
+
system! command
|
365
|
+
git "status", "-sb", "-uno", path
|
366
|
+
#run_action "branch:echo", "CURR"
|
367
|
+
end
|
368
|
+
|
369
|
+
status_optset = optionset {
|
370
|
+
@option.(:trackedonly, "-U", "ignore untracked files")
|
371
|
+
}
|
372
|
+
|
373
|
+
@action.("show status in compact format")
|
374
|
+
@optionset.(status_optset)
|
375
|
+
def compact(*path, trackedonly: false)
|
376
|
+
opts = trackedonly ? ["-uno"] : []
|
377
|
+
git "status", "-sb", *opts, *path
|
378
|
+
end
|
379
|
+
|
380
|
+
@action.("show status in default format")
|
381
|
+
@optionset.(status_optset)
|
382
|
+
def default(*path, trackedonly: false)
|
383
|
+
opts = trackedonly ? ["-uno"] : []
|
384
|
+
git "status", *opts, *path
|
385
|
+
end
|
386
|
+
|
387
|
+
end
|
388
|
+
|
389
|
+
define_alias "status", "status:compact"
|
390
|
+
|
391
|
+
|
392
|
+
##
|
393
|
+
## branch:
|
394
|
+
##
|
395
|
+
category "branch:" do
|
396
|
+
|
397
|
+
@action.("list branches")
|
398
|
+
@option.(:all , "-a, --all" , "list both local and remote branches (default)")
|
399
|
+
@option.(:remote, "-r, --remote", "list remote branches")
|
400
|
+
@option.(:local , "-l, --local" , "list local branches")
|
401
|
+
def list(all: false, remote: false, local: false)
|
402
|
+
opt = remote ? "-r" : local ? "-l" : "-a"
|
403
|
+
git "branch", opt
|
404
|
+
end
|
405
|
+
|
406
|
+
@action.("switch to previous or other branch", important: true)
|
407
|
+
def switch(branch=nil)
|
408
|
+
branch = resolve_except_prev_branch(branch)
|
409
|
+
git "checkout", branch
|
410
|
+
#git "switch", branch
|
411
|
+
end
|
412
|
+
|
413
|
+
@action.("create a new branch, not switch to it")
|
414
|
+
@option.(:on, "--on=<commit>", "commit-id on where the new branch will be created")
|
415
|
+
@option.(:switch, "-w, --switch", "switch to the new branch after created")
|
416
|
+
def create(branch, on: nil, switch: false)
|
417
|
+
args = on ? [on] : []
|
418
|
+
git "branch", branch, *args
|
419
|
+
git "checkout", branch if switch
|
420
|
+
end
|
421
|
+
|
422
|
+
@action.("create a new branch and switch to it", important: true)
|
423
|
+
@option.(:on, "--on=<commit>", "commit-id on where the new branch will be created")
|
424
|
+
def fork(branch, on: nil)
|
425
|
+
args = on ? [on] : []
|
426
|
+
git "checkout", "-b", branch, *args
|
427
|
+
end
|
428
|
+
|
429
|
+
mergeopts = optionset {
|
430
|
+
@option.(:delete , "-d, --delete", "delete the current branch after merged")
|
431
|
+
@option.(:fastforward, " --ff", "use fast-forward merge")
|
432
|
+
@option.(:reuse , "-M", "reuse commit message (not invoke text editor for it)")
|
433
|
+
}
|
434
|
+
|
435
|
+
@action.("merge current branch into previous or other branch", important: true)
|
436
|
+
@optionset.(mergeopts)
|
437
|
+
def join(branch=nil, delete: false, fastforward: false, reuse: false)
|
438
|
+
into_branch = resolve_branch(branch || "PREV")
|
439
|
+
__merge(curr_branch(), into_branch, true, fastforward, delete, reuse)
|
440
|
+
end
|
441
|
+
|
442
|
+
@action.("merge previous or other branch into current branch")
|
443
|
+
@optionset.(mergeopts)
|
444
|
+
def merge(branch=nil, delete: false, fastforward: false, reuse: false)
|
445
|
+
merge_branch = resolve_branch(branch || "PREV")
|
446
|
+
__merge(merge_branch, curr_branch(), false, fastforward, delete, reuse)
|
447
|
+
|
448
|
+
end
|
449
|
+
|
450
|
+
def __merge(merge_branch, into_branch, switch, fastforward, delete, reuse)
|
451
|
+
b = proc {|s| "'\e[1m#{s}\e[0m'" } # bold font
|
452
|
+
#msg = "Merge #{b.(merge_branch)} branch into #{b.(into_branch)}?"
|
453
|
+
msg = switch \
|
454
|
+
? "Merge current branch #{b.(merge_branch)} into #{b.(into_branch)}." \
|
455
|
+
: "Merge #{b.(merge_branch)} branch into #{b.(into_branch)}."
|
456
|
+
if confirm(msg + " OK?")
|
457
|
+
_check_fastforward_merge_available(into_branch, merge_branch)
|
458
|
+
opts = fastforward ? ["--ff-only"] : ["--no-ff"]
|
459
|
+
opts << "--no-edit" if reuse
|
460
|
+
git "checkout", into_branch if switch
|
461
|
+
git "merge", *opts, (switch ? "-" : merge_branch)
|
462
|
+
git "branch", "-d", merge_branch if delete
|
463
|
+
else
|
464
|
+
puts "** Not joined." if switch
|
465
|
+
puts "** Not merged." unless switch
|
466
|
+
end
|
467
|
+
end
|
468
|
+
private :__merge
|
469
|
+
|
470
|
+
def _check_fastforward_merge_available(parent_branch, child_branch)
|
471
|
+
parent, child = parent_branch, child_branch
|
472
|
+
cmd = "git merge-base --is-ancestor #{parent} #{child}"
|
473
|
+
result_ok = system cmd
|
474
|
+
result_ok or
|
475
|
+
raise action_error("Cannot merge '#{child}' branch; rebase it onto '#{parent}' in advance.")
|
476
|
+
end
|
477
|
+
private :_check_fastforward_merge_available
|
478
|
+
|
479
|
+
@action.("create a new local branch from a remote branch")
|
480
|
+
@option.(:remote, "--remote=<remote>", "remote repository name (default: origin)")
|
481
|
+
def checkout(branch, remote: "origin")
|
482
|
+
local_branch = branch
|
483
|
+
remote_branch = "#{remote}/#{branch}"
|
484
|
+
git "checkout", "-b", local_branch, remote_branch
|
485
|
+
end
|
486
|
+
|
487
|
+
@action.("rename the current branch to other name")
|
488
|
+
@option.(:target, "-t <branch>", "target branch instead of current branch")
|
489
|
+
def rename(new_branch, target: nil)
|
490
|
+
old_branch = target || curr_branch()
|
491
|
+
git "branch", "-m", old_branch, new_branch
|
492
|
+
end
|
493
|
+
|
494
|
+
@action.("delete a branch")
|
495
|
+
@option.(:force, "-f, --force", "delete forcedly even if not merged")
|
496
|
+
@option.(:remote, "-r, --remote[=origin]", "delete a remote branch")
|
497
|
+
def delete(branch, force: false, remote: nil)
|
498
|
+
if branch == nil
|
499
|
+
branch = curr_branch()
|
500
|
+
yes = confirm "Are you sure to delete current branch '#{branch}'?", default_yes: false
|
501
|
+
return unless yes
|
502
|
+
git "checkout", "-" unless remote
|
503
|
+
else
|
504
|
+
branch = resolve_branch(branch)
|
505
|
+
end
|
506
|
+
if remote
|
507
|
+
remote = "origin" if remote == true
|
508
|
+
opts = force ? ["-f"] : []
|
509
|
+
#git "push", *opts, remote, ":#{branch}"
|
510
|
+
git "push", "--delete", *opts, remote, branch
|
511
|
+
else
|
512
|
+
opts = force ? ["-D"] : ["-d"]
|
513
|
+
git "branch", *opts, branch
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
@action.("change commit-id of current HEAD")
|
518
|
+
@option.(:restore, "--restore", "restore files after reset")
|
519
|
+
def reset(commit, restore: false)
|
520
|
+
opts = []
|
521
|
+
opts << "--hard" if restore
|
522
|
+
git "reset", *opts, commit
|
523
|
+
end
|
524
|
+
|
525
|
+
@action.("rebase (move) current branch on top of other branch")
|
526
|
+
@option.(:from, "--from=<commit-id>", "commit-id where current branch started")
|
527
|
+
def rebase(branch_onto, branch_upstream=nil, from: nil)
|
528
|
+
br_onto = resolve_branch(branch_onto)
|
529
|
+
if from
|
530
|
+
git "rebase", "--onto=#{br_onto}", from+"^"
|
531
|
+
elsif branch_upstream
|
532
|
+
git "rebase", "--onto=#{br_onto}", resolve_branch(branch_upstream)
|
533
|
+
else
|
534
|
+
git "rebase", br_onto
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
@action.("git pull && git stash && git rebase && git stash pop")
|
539
|
+
@option.(:rebase, "-b, --rebase", "rebase if prev branch updated")
|
540
|
+
def update(branch=nil, rebase: false)
|
541
|
+
if curr_branch() == GIT_CONFIG.initial_branch
|
542
|
+
git "pull"
|
543
|
+
return
|
544
|
+
end
|
545
|
+
#
|
546
|
+
branch ||= prev_branch()
|
547
|
+
remote = remote_repo_of_branch(branch) or
|
548
|
+
raise action_error("Previous branch '#{branch}' has no remote repo. (Hint: run `gi branch:upstream -t #{branch} origin`.)")
|
549
|
+
puts "[INFO] previous: #{branch}, remote: #{remote}" unless $QUIET_MODE
|
550
|
+
#
|
551
|
+
git "fetch"
|
552
|
+
file_changed = ! `git diff`.empty?
|
553
|
+
remote_updated = ! _same_commit_id?(branch, "#{remote}/#{branch}")
|
554
|
+
rebase_required = ! `git log --oneline HEAD..#{branch}`.empty?
|
555
|
+
if remote_updated || (rebase && rebase_required)
|
556
|
+
git "stash", "push", "-q" if file_changed
|
557
|
+
if remote_updated
|
558
|
+
git "checkout", "-q", branch
|
559
|
+
#git "reset", "--hard", "#{remote}/#{branch}"
|
560
|
+
git "pull"
|
561
|
+
git "checkout", "-q", "-"
|
562
|
+
end
|
563
|
+
git "rebase", branch if rebase
|
564
|
+
git "stash", "pop", "-q" if file_changed
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
@action.("print upstream repo name of current branch")
|
569
|
+
def upstream()
|
570
|
+
#git! "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"
|
571
|
+
branch = curr_branch()
|
572
|
+
echoback "git config --get-regexp '^branch\\.#{branch}\\.remote' | awk '{print $2}'"
|
573
|
+
output = `git config --get-regexp '^branch\\.#{branch}\\.remote'`
|
574
|
+
output.each_line {|line| puts line.split()[1] }
|
575
|
+
end
|
576
|
+
|
577
|
+
@action.("show current branch name")
|
578
|
+
def current()
|
579
|
+
git "rev-parse", "--abbrev-ref", "HEAD"
|
580
|
+
#git "symbolic-ref", "--short", "HEAD"
|
581
|
+
#git "branch", "--show-current"
|
582
|
+
end
|
583
|
+
|
584
|
+
@action.("show previous branch name")
|
585
|
+
def previous()
|
586
|
+
#git "rev-parse", "--symbolic-full-name", "@{-1}"
|
587
|
+
git "rev-parse", "--abbrev-ref", "@{-1}"
|
588
|
+
end
|
589
|
+
|
590
|
+
@action.("show parent branch name (EXPERIMENTAL)")
|
591
|
+
def parent()
|
592
|
+
# ref: https://stackoverflow.com/questions/3161204/
|
593
|
+
command = <<~'END'
|
594
|
+
git show-branch -a \
|
595
|
+
| sed 's/].*//' \
|
596
|
+
| grep '\*' \
|
597
|
+
| grep -v "\\[$(git branch --show-current)\$" \
|
598
|
+
| head -n1 \
|
599
|
+
| sed 's/^.*\[//'
|
600
|
+
END
|
601
|
+
echoback(command.gsub(/\\\n/, '').strip())
|
602
|
+
puts parent_branch()
|
603
|
+
end
|
604
|
+
|
605
|
+
@action.("print CURR/PREV/PARENT branch name")
|
606
|
+
def echo(branch)
|
607
|
+
case branch
|
608
|
+
when "CURR" ; run_action "current"
|
609
|
+
when "PREV", "-" ; run_action "previous"
|
610
|
+
when "PARENT" ; run_action "parent" # (EXPERIMENTAL)
|
611
|
+
else ; git "rev-parse", "--abbrev-ref", branch
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
end
|
616
|
+
|
617
|
+
define_alias("branches", "branch:list")
|
618
|
+
define_alias("branch" , "branch:create")
|
619
|
+
define_alias("switch" , "branch:switch")
|
620
|
+
define_alias("sw" , "branch:switch")
|
621
|
+
define_alias("fork" , "branch:fork")
|
622
|
+
define_alias("join" , "branch:join")
|
623
|
+
define_alias("merge" , "branch:merge")
|
624
|
+
define_alias("update" , "branch:update")
|
625
|
+
|
626
|
+
|
627
|
+
##
|
628
|
+
## file:
|
629
|
+
##
|
630
|
+
category "file:" do
|
631
|
+
|
632
|
+
@action.("list (un)tracked/ignored/missing files")
|
633
|
+
@option.(:filtertype, "-F <filtertype>", "one of:", detail: <<~END)
|
634
|
+
- tracked : only tracked files (default)
|
635
|
+
- untracked : only not-tracked files
|
636
|
+
- ignored : ignored files by '.gitignore'
|
637
|
+
- missing : tracked but missing files
|
638
|
+
END
|
639
|
+
@option.(:full, "--full", "show full list")
|
640
|
+
def list(path=".", filtertype: "tracked", full: false)
|
641
|
+
method_name = "__file__list__#{filtertype}"
|
642
|
+
respond_to?(method_name, true) or (
|
643
|
+
s = self.private_methods.grep(/^__file__list__(.*)/) { $1 }.join('/')
|
644
|
+
raise option_error("#{filtertype}: Uknown filter type (expected: #{s}})")
|
645
|
+
)
|
646
|
+
__send__(method_name, path, full)
|
647
|
+
end
|
648
|
+
|
649
|
+
private
|
650
|
+
|
651
|
+
def __file__list__tracked(path, full)
|
652
|
+
paths = path ? [path] : []
|
653
|
+
git "ls-files", *paths
|
654
|
+
end
|
655
|
+
|
656
|
+
def __file__list__untracked(path, full)
|
657
|
+
opt = full ? " -u" : nil
|
658
|
+
echoback "git status -s#{opt} #{path} | grep '^?? '"
|
659
|
+
output = `git status -s#{opt} #{path}`
|
660
|
+
puts output.each_line().grep(/^\?\? /)
|
661
|
+
end
|
662
|
+
|
663
|
+
def __file__list__ignored(path, full)
|
664
|
+
opt = full ? "--ignored=matching" : "--ignored"
|
665
|
+
echoback "git status -s #{opt} #{path} | grep '^!! '"
|
666
|
+
output = `git status -s #{opt} #{path}`
|
667
|
+
puts output.each_line().grep(/^!! /)
|
668
|
+
end
|
669
|
+
|
670
|
+
def __file__list__missing(path, full)
|
671
|
+
paths = path ? [path] : []
|
672
|
+
git "ls-files", "--deleted", *paths
|
673
|
+
end
|
674
|
+
|
675
|
+
public
|
676
|
+
|
677
|
+
## TODO: should move to 'file:' category?
|
678
|
+
@action.("register files into the repository", important: true)
|
679
|
+
@option.(:force, "-f, --force", "allow to track ignored files")
|
680
|
+
@option.(:recursive, "-r, --recursive", "track files under directories")
|
681
|
+
#@option.(:allow_empty_dir, "-e, --allow-empty-dir", "create '.gitkeep' to track empty directory")
|
682
|
+
def track(file, *file2, force: false, recursive: false)
|
683
|
+
files = [file] + file2
|
684
|
+
files.each do |x|
|
685
|
+
output = `git ls-files -- #{x}`
|
686
|
+
output.empty? or
|
687
|
+
raise action_error("#{x}: Already tracked.")
|
688
|
+
end
|
689
|
+
files.each do |x|
|
690
|
+
if File.directory?(x)
|
691
|
+
recursive or
|
692
|
+
raise action_error("#{x}: File expected, but is a directory (specify `-r` or `--recursive` otpion to track files under the directory).")
|
693
|
+
end
|
694
|
+
end
|
695
|
+
opts = force ? ["-f"] : []
|
696
|
+
git "add", *opts, *files
|
697
|
+
end
|
698
|
+
|
699
|
+
@action.("show changes of files", important: true)
|
700
|
+
def changes(*path)
|
701
|
+
git "diff", *path
|
702
|
+
end
|
703
|
+
|
704
|
+
@action.("move files into a directory")
|
705
|
+
@option.(:to, "--to=<dir>", "target directory")
|
706
|
+
def move(file, *file2, to: nil)
|
707
|
+
dir = to
|
708
|
+
dir != nil or
|
709
|
+
raise option_error("Option `--to=<dir>` required.")
|
710
|
+
File.exist?(dir) or
|
711
|
+
raise option_error("--to=#{dir}: Directory not exist (create it first).")
|
712
|
+
File.directory?(dir) or
|
713
|
+
raise option_error("--to=#{dir}: Not a directory (to rename files, use 'file:rename' action instead).")
|
714
|
+
files = [file] + file2
|
715
|
+
git "mv", *files, dir
|
716
|
+
end
|
717
|
+
|
718
|
+
@action.("rename a file or directory to new name")
|
719
|
+
def rename(old_file, new_file)
|
720
|
+
! File.exist?(new_file) or
|
721
|
+
raise action_failed("#{new_file}: Already exist.")
|
722
|
+
git "mv", old_file, new_file
|
723
|
+
end
|
724
|
+
|
725
|
+
@action.("delete files or directories")
|
726
|
+
@option.(:recursive, "-r, --recursive", "delete files recursively.")
|
727
|
+
def delete(file, *file2, recursive: false)
|
728
|
+
files = [file] + file2
|
729
|
+
opts = recursive ? ["-r"] : []
|
730
|
+
git "rm", *opts, *files
|
731
|
+
end
|
732
|
+
|
733
|
+
@action.("restore files (= clear changes)", important: true)
|
734
|
+
def restore(*path)
|
735
|
+
if path.empty?
|
736
|
+
git "reset", "--hard"
|
737
|
+
#git "checkout", "--", "." # path required
|
738
|
+
else
|
739
|
+
#git "reset", "--hard", "--", *path #=> fatal: Cannot do hard reset with paths.
|
740
|
+
git "checkout", "--", *path
|
741
|
+
end
|
742
|
+
end
|
743
|
+
|
744
|
+
@action.("print commit-id, author, and timestap of each line")
|
745
|
+
@option.(:range, "-L <N1,N2|:func>", "range (start,end) or function name")
|
746
|
+
def blame(path, *path2, range: nil)
|
747
|
+
paths = [path] + path2
|
748
|
+
opts = []
|
749
|
+
opts << "-L" << range if range
|
750
|
+
git "blame", *opts, *paths
|
751
|
+
end
|
752
|
+
|
753
|
+
@action.("find by pattern")
|
754
|
+
def egrep(pattern, commit=nil)
|
755
|
+
args = []
|
756
|
+
args << commit if commit
|
757
|
+
git "grep", "-E", pattern, *args
|
758
|
+
end
|
759
|
+
|
760
|
+
end
|
761
|
+
|
762
|
+
define_alias("files" , "file:list")
|
763
|
+
#define_alias("ls" , "file:list")
|
764
|
+
define_alias("track" , "file:track")
|
765
|
+
define_alias("register" , "file:track")
|
766
|
+
define_alias("changes" , "file:changes")
|
767
|
+
#define_alias("move" , "file:move")
|
768
|
+
#define_alias("rename" , "file:rename")
|
769
|
+
#define_alias("delete" , "file:delete")
|
770
|
+
#define_alias("restore" , "file:restore")
|
771
|
+
|
772
|
+
|
773
|
+
##
|
774
|
+
## staging:
|
775
|
+
##
|
776
|
+
category "staging:" do
|
777
|
+
|
778
|
+
@action.("add changes of files into staging area", important: true)
|
779
|
+
@option.(:pick, "-p, --pick", "pick up changes interactively")
|
780
|
+
#@option.(:update, "-u, --update", "add all changes of tracked files")
|
781
|
+
def add(path, *path2, pick: false) # , update: false
|
782
|
+
paths = [path] + path2
|
783
|
+
paths.each do |x|
|
784
|
+
next if File.directory?(x)
|
785
|
+
output = `git ls-files #{x}`
|
786
|
+
! output.strip.empty? or
|
787
|
+
raise action_error("#{x}: Not tracked yet (run 'track' action instead).")
|
788
|
+
end
|
789
|
+
#
|
790
|
+
opts = []
|
791
|
+
opts << "-p" if pick
|
792
|
+
opts << "-u" unless pick
|
793
|
+
git "add", *opts, *paths
|
794
|
+
end
|
795
|
+
|
796
|
+
@action.("show changes in staging area", important: true)
|
797
|
+
def show(*path)
|
798
|
+
git "diff", "--cached", *path
|
799
|
+
end
|
800
|
+
|
801
|
+
@action.("edit changes in staging area")
|
802
|
+
def edit(*path)
|
803
|
+
git "add", "--edit", *path
|
804
|
+
end
|
805
|
+
|
806
|
+
@action.("delete all changes in staging area", important: true)
|
807
|
+
def clear(*path)
|
808
|
+
args = path.empty? ? [] : ["--"] + path
|
809
|
+
git "reset", "HEAD", *args
|
810
|
+
end
|
811
|
+
|
812
|
+
end
|
813
|
+
|
814
|
+
define_alias("stage" , "staging:add")
|
815
|
+
define_alias("staged" , "staging:show")
|
816
|
+
define_alias("unstage" , "staging:clear")
|
817
|
+
define_alias("pick" , ["staging:add", "-p"])
|
818
|
+
|
819
|
+
|
820
|
+
##
|
821
|
+
## commit:
|
822
|
+
##
|
823
|
+
category "commit:" do
|
824
|
+
|
825
|
+
@action.("create a new commit", important: true)
|
826
|
+
#@option.(:message, "-m, --message=<message>", "commit message")
|
827
|
+
@option.(:file, "-f, --file=<file>", "commit message file")
|
828
|
+
def create(message=nil, *path, file: nil)
|
829
|
+
opts = []
|
830
|
+
opts << "-m" << message if message && ! message.empty?
|
831
|
+
opts << "--file=#{file}" if file
|
832
|
+
args = path.empty? ? [] : ["--", *path]
|
833
|
+
git "commit", *opts, *args
|
834
|
+
end
|
835
|
+
|
836
|
+
@action.("correct the last commit", important: true)
|
837
|
+
@option.(:reuse, "-M", "reuse commit message (not invoke text editor for it)")
|
838
|
+
def correct(reuse: false)
|
839
|
+
opts = reuse ? ["--no-edit"] : []
|
840
|
+
git "commit", "--amend", *opts
|
841
|
+
end
|
842
|
+
|
843
|
+
@action.("correct the previous commit")
|
844
|
+
@option.(:histedit, "-e, --histedit", "start 'history:edit' action after fixup commit created")
|
845
|
+
def fixup(commit, histedit: nil)
|
846
|
+
git "commit", "--fixup=#{commit}"
|
847
|
+
if histedit
|
848
|
+
run_once "history:edit:start", "#{commit}^"
|
849
|
+
end
|
850
|
+
end
|
851
|
+
|
852
|
+
@action.("apply a commit to curr branch (known as 'cherry-pick')")
|
853
|
+
def apply(commit, *commit2)
|
854
|
+
commits = [commit] + commit2
|
855
|
+
git "cherry-pick", *commits
|
856
|
+
end
|
857
|
+
|
858
|
+
@action.("show commits in current branch", important: true)
|
859
|
+
@option.(:count, "-n <N>", "show latest N commits", type: Integer)
|
860
|
+
@option.(:file, "-f, --file=<path>", "show commits related to file")
|
861
|
+
def show(commit=nil, count: nil, file: nil)
|
862
|
+
if count && commit
|
863
|
+
git "show", "#{commit}~#{count}..#{commit}"
|
864
|
+
elsif count
|
865
|
+
git "show", "HEAD~#{count}..HEAD"
|
866
|
+
elsif commit
|
867
|
+
git "show", commit
|
868
|
+
elsif file
|
869
|
+
git "log", "-p", "--", file
|
870
|
+
else
|
871
|
+
git "log", "-p"
|
872
|
+
end
|
873
|
+
end
|
874
|
+
|
875
|
+
@action.("create a new commit which reverts the target commit")
|
876
|
+
@option.(:count, "-n <N>", "show latest N commits", type: Integer)
|
877
|
+
@option.(:mainline, "--mainline=<N>", "parent number (necessary to revert merge commit)")
|
878
|
+
@option.(:reuse, "-M", "reuse commit message (not invoke text editor for it)")
|
879
|
+
def revert(*commit, count: nil, mainline: nil, reuse: false)
|
880
|
+
commits = commit
|
881
|
+
opts = []
|
882
|
+
opts << "--no-edit" if reuse
|
883
|
+
opts << "-m" << mainline.to_s if mainline
|
884
|
+
if count
|
885
|
+
commits.length <= 1 or
|
886
|
+
raise action_error("Multiple commits are not allowed when '-n' option specified.")
|
887
|
+
commit = commits.empty? ? "HEAD" : commits[0]
|
888
|
+
git "revert", *opts, "#{commit}~#{count}..#{commit}"
|
889
|
+
elsif ! commits.empty?
|
890
|
+
git "revert", *opts, *commits
|
891
|
+
else
|
892
|
+
raise action_error("`<commit-id>` or `-n <N>` option required.")
|
893
|
+
end
|
894
|
+
end
|
895
|
+
|
896
|
+
@action.("cancel recent commits up to the target commit-id", important: true)
|
897
|
+
@option.(:count , "-n <N>" , "cancel recent N commits", type: Integer)
|
898
|
+
@option.(:restore, "--restore", "restore files after rollback")
|
899
|
+
def rollback(commit=nil, count: nil, restore: false)
|
900
|
+
opts = restore ? ["--hard"] : []
|
901
|
+
if commit && count
|
902
|
+
raise action_failed("Commit-id and `-n` option are exclusive.")
|
903
|
+
elsif commit
|
904
|
+
git "reset", *opts, commit
|
905
|
+
elsif count
|
906
|
+
git "reset", *opts, "HEAD~#{count}"
|
907
|
+
else
|
908
|
+
git "reset", *opts, "HEAD^"
|
909
|
+
end
|
910
|
+
end
|
911
|
+
|
912
|
+
end
|
913
|
+
|
914
|
+
define_alias("commit" , "commit:create")
|
915
|
+
define_alias("cc" , "commit:create")
|
916
|
+
define_alias("correct" , "commit:correct")
|
917
|
+
define_alias("fixup" , "commit:fixup")
|
918
|
+
define_alias("commits" , "commit:show")
|
919
|
+
#define_alias("rollback", "commit:rollback")
|
920
|
+
|
921
|
+
|
922
|
+
##
|
923
|
+
## history:
|
924
|
+
##
|
925
|
+
category "history:", action: "show" do
|
926
|
+
|
927
|
+
@action.("show commit history in various format", important: true)
|
928
|
+
@option.(:all, "-a, --all" , "show history of all branches")
|
929
|
+
@option.(:format, "-F, --format=<format>", "default/oneline/fuller/graph")
|
930
|
+
@option.(:author, "-u, --author", "show author name before '@' of email address (only for 'graph' format)")
|
931
|
+
def show(*path, all: false, format: "default", author: false)
|
932
|
+
opts = []
|
933
|
+
HISTORY_SHOW_OPTIONS.key?(format) or
|
934
|
+
raise option_error("#{format}: Unknown format.")
|
935
|
+
val = HISTORY_SHOW_OPTIONS[format]
|
936
|
+
case val
|
937
|
+
when nil ;
|
938
|
+
when String ; opts << val
|
939
|
+
when Array ; opts.concat(val)
|
940
|
+
when Proc ; opts.concat([val.call(author: author)].flatten)
|
941
|
+
else
|
942
|
+
raise TypeError.new("HISTORY_SHOW_OPTIONS[#{format.inspect}]: Unexpected type value: #{val.inspect}")
|
943
|
+
end
|
944
|
+
opts = ["--all"] + opts if all
|
945
|
+
## use 'git!' to ignore pipe error when pager process quitted
|
946
|
+
git! "log", *opts, *path
|
947
|
+
end
|
948
|
+
|
949
|
+
HISTORY_SHOW_OPTIONS = {
|
950
|
+
"default" => nil,
|
951
|
+
"compact" => "--oneline",
|
952
|
+
"oneline" => "--oneline",
|
953
|
+
"detailed" => "--format=fuller",
|
954
|
+
"fuller" => "--format=fuller",
|
955
|
+
"graph" => proc {|author: false, **_kws|
|
956
|
+
fmt = GIT_CONFIG.history_graph_format
|
957
|
+
fmt = fmt.sub(/ ?<?%a[eEnNlL]>? ?/, ' ') unless author
|
958
|
+
opts = ["--format=#{fmt}"] + GIT_CONFIG.history_graph_options
|
959
|
+
opts
|
960
|
+
},
|
961
|
+
}
|
962
|
+
|
963
|
+
@action.("show commits not uploaded yet")
|
964
|
+
def notuploaded()
|
965
|
+
git "cherry", "-v"
|
966
|
+
end
|
967
|
+
|
968
|
+
## history:edit
|
969
|
+
category "edit:" do
|
970
|
+
|
971
|
+
@action.("start `git rebase -i` to edit commit history", important: true)
|
972
|
+
@option.(:count , "-n, --num=<N>", "edit last N commits")
|
973
|
+
#@option.(:stash, "-s, --stash", "store current changes into stash temporarily")
|
974
|
+
def start(commit=nil, count: nil)
|
975
|
+
if commit && count
|
976
|
+
raise action_error("Commit-id and `-n` option are exclusive.")
|
977
|
+
elsif commit
|
978
|
+
nil
|
979
|
+
arg = "#{commit}^"
|
980
|
+
elsif count
|
981
|
+
arg = "HEAD~#{count}"
|
982
|
+
else
|
983
|
+
raise action_error("Commit-id or `-n` option required.")
|
984
|
+
end
|
985
|
+
git "rebase", "-i", "--autosquash", arg
|
986
|
+
end
|
987
|
+
|
988
|
+
@action.("resume (= conitnue) suspended `git rebase -i`")
|
989
|
+
def resume()
|
990
|
+
git "rebase", "--continue"
|
991
|
+
end
|
992
|
+
|
993
|
+
@action.("skip current commit and resume")
|
994
|
+
def skip()
|
995
|
+
git "rebase", "--skip"
|
996
|
+
end
|
997
|
+
|
998
|
+
@action.("cancel (or abort) `git rebase -i`")
|
999
|
+
def cancel()
|
1000
|
+
git "rebase", "--abort"
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
define_alias "hist" , ["history", "-F", "graph"]
|
1008
|
+
#define_alias "history" , "history:show"
|
1009
|
+
define_alias "histedit" , "history:edit:start"
|
1010
|
+
#define_alias "histedit:resume", "history:edit:resume"
|
1011
|
+
#define_alias "histedit:skip" , "history:edit:skip"
|
1012
|
+
#define_alias "histedit:cancel", "history:edit:cancel"
|
1013
|
+
|
1014
|
+
|
1015
|
+
##
|
1016
|
+
## repo:
|
1017
|
+
##
|
1018
|
+
category "repo:" do
|
1019
|
+
|
1020
|
+
def _config_user_and_email(user, email)
|
1021
|
+
if user == nil && `git config --get user.name`.strip().empty?
|
1022
|
+
user = ask_to_user "User name:"
|
1023
|
+
end
|
1024
|
+
git "config", "user.name" , user if user
|
1025
|
+
if email == nil && `git config --get user.email`.strip().empty?
|
1026
|
+
email = ask_to_user "Email address:"
|
1027
|
+
end
|
1028
|
+
git "config", "user.email", email if email
|
1029
|
+
end
|
1030
|
+
private :_config_user_and_email
|
1031
|
+
|
1032
|
+
def _generate_gitignore_file(filename)
|
1033
|
+
items = GIT_CONFIG.gitignore_items
|
1034
|
+
sep = "> "
|
1035
|
+
items.each do |x|
|
1036
|
+
echoback "echo %-14s %s %s" % ["'#{x}'", sep, filename]
|
1037
|
+
sep = ">>"
|
1038
|
+
end
|
1039
|
+
content = (items + [""]).join("\n")
|
1040
|
+
File.write(filename, content, encoding: 'utf-8')
|
1041
|
+
end
|
1042
|
+
private :_generate_gitignore_file
|
1043
|
+
|
1044
|
+
initopts = optionset() {
|
1045
|
+
@option.(:initial_branch, "-b, --branch=<branch>", "branch name (default: '#{GIT_CONFIG.initial_branch}')")
|
1046
|
+
@option.(:user , "-u, --user=<user>", "user name")
|
1047
|
+
@option.(:email, "-e, --email=<email>", "email address")
|
1048
|
+
@option.(:initial_commit, "-x", "not create an empty initial commit", value: false)
|
1049
|
+
}
|
1050
|
+
|
1051
|
+
@action.("initialize git repository with empty initial commit", important: true)
|
1052
|
+
@optionset.(initopts)
|
1053
|
+
def init(user: nil, email: nil, initial_branch: nil, initial_commit: true)
|
1054
|
+
! File.exist?(".git") or
|
1055
|
+
raise action_error("Directory '.git' already exists.")
|
1056
|
+
branch ||= GIT_CONFIG.initial_branch
|
1057
|
+
git "init", "--initial-branch=#{branch}"
|
1058
|
+
_config_user_and_email(user, email)
|
1059
|
+
if initial_commit
|
1060
|
+
git "commit", "--allow-empty", "-m", GIT_CONFIG.initial_commit_message
|
1061
|
+
end
|
1062
|
+
filename = ".gitignore"
|
1063
|
+
_generate_gitignore_file(filename) unless File.exist?(filename)
|
1064
|
+
end
|
1065
|
+
|
1066
|
+
@action.("create a new directory and initialize it as a git repo")
|
1067
|
+
@optionset.(initopts)
|
1068
|
+
def create(name, user: nil, email: nil, initial_branch: nil, initial_commit: true)
|
1069
|
+
dir = name
|
1070
|
+
mkdir dir
|
1071
|
+
cd dir do
|
1072
|
+
run_once "init", user: user, email: email, initial_branch: initial_branch, initial_commit: initial_commit
|
1073
|
+
end
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
@action.("copy a repository ('github:<user>/<repo>' is available)")
|
1077
|
+
@optionset.(initopts.select(:user, :email))
|
1078
|
+
def clone(url, dir=nil, user: nil, email: nil)
|
1079
|
+
url = resolve_repository_url(url)
|
1080
|
+
args = dir ? [dir] : []
|
1081
|
+
files = Dir.glob("*")
|
1082
|
+
git "clone", url, *args
|
1083
|
+
newdir = (Dir.glob("*") - files)[0] || dir
|
1084
|
+
cd newdir do
|
1085
|
+
_config_user_and_email(user, email)
|
1086
|
+
end if newdir
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
## repo:remote:
|
1090
|
+
category "remote:", action: "handle" do
|
1091
|
+
|
1092
|
+
@action.("list/get/set/delete remote repository", usage: [
|
1093
|
+
" # list",
|
1094
|
+
"<name> # get",
|
1095
|
+
"<name> <url> # set ('github:user/repo' is avaialble)",
|
1096
|
+
"<name> \"\" # delete",
|
1097
|
+
], postamble: {
|
1098
|
+
"Example:" => <<~'END'.gsub(/^/, " "),
|
1099
|
+
$ gi repo:remote # list
|
1100
|
+
$ gi repo:remote origin # get
|
1101
|
+
$ gi repo:remote origin github:user1/repo1 # set
|
1102
|
+
$ gi repo:remote origin "" # delete
|
1103
|
+
END
|
1104
|
+
})
|
1105
|
+
def handle(name=nil, url=nil)
|
1106
|
+
url = resolve_repository_url(url) if url
|
1107
|
+
if name == nil
|
1108
|
+
git "remote", "-v"
|
1109
|
+
elsif url == nil
|
1110
|
+
git "remote", "get-url", name
|
1111
|
+
elsif url == ""
|
1112
|
+
git "remote", "remove", name
|
1113
|
+
elsif `git remote`.split().include?(name)
|
1114
|
+
git "remote", "set-url", name, url
|
1115
|
+
else
|
1116
|
+
git "remote", "add", name, url
|
1117
|
+
end
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
@action.("get/set/delete origin (= default remote repository)", usage: [
|
1121
|
+
" # get",
|
1122
|
+
"<url> # set ('github:user/repo' is avaialble)",
|
1123
|
+
"\"\" # delete",
|
1124
|
+
], postamble: {
|
1125
|
+
"Example:" => <<~'END'.gsub(/^/, " "),
|
1126
|
+
$ gi repo:remote:origin # get
|
1127
|
+
$ gi repo:remote:origin github:user1/repo1 # set
|
1128
|
+
$ gi repo:remote:origin "" # delete
|
1129
|
+
END
|
1130
|
+
})
|
1131
|
+
def origin(url=nil)
|
1132
|
+
run_action "repo:remote", "origin", url
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
end
|
1138
|
+
|
1139
|
+
|
1140
|
+
##
|
1141
|
+
## tag:
|
1142
|
+
##
|
1143
|
+
category "tag:", action: "handle" do
|
1144
|
+
|
1145
|
+
@action.("list/show/create/delete tags", important: true, usage: [
|
1146
|
+
" # list",
|
1147
|
+
"<tag> # show commit-id of the tag",
|
1148
|
+
"<tag> <commit> # create a tag on the commit",
|
1149
|
+
"<tag> HEAD # create a tag on current commit",
|
1150
|
+
"<tag> \"\" # delete a tag",
|
1151
|
+
])
|
1152
|
+
@option.(:remote, "-r, --remote[=origin]", "list/delete tags on remote (not for show/create)")
|
1153
|
+
def handle(tag=nil, commit=nil, remote: nil)
|
1154
|
+
if tag == nil # list
|
1155
|
+
if remote
|
1156
|
+
#git "show-ref", "--tags"
|
1157
|
+
git "ls-remote", "--tags"
|
1158
|
+
else
|
1159
|
+
git "tag", "-l"
|
1160
|
+
end
|
1161
|
+
elsif commit == nil # show
|
1162
|
+
! remote or
|
1163
|
+
raise option_error("Option '-r' or '--remote' is not available for showing tag.")
|
1164
|
+
git "rev-parse", tag
|
1165
|
+
elsif commit == "" # delete
|
1166
|
+
if remote
|
1167
|
+
remote = "origin" if remote == true
|
1168
|
+
#git "push", "--delete", remote, tag # may delete same name branch
|
1169
|
+
git "push", remote, ":refs/tags/#{tag}"
|
1170
|
+
else
|
1171
|
+
git "tag", "--delete", tag
|
1172
|
+
end
|
1173
|
+
else # create
|
1174
|
+
! remote or
|
1175
|
+
raise option_error("Option '-r' or '--remote' is not available for creating tag.")
|
1176
|
+
git "tag", tag, commit
|
1177
|
+
end
|
1178
|
+
end
|
1179
|
+
|
1180
|
+
@action.("list tags")
|
1181
|
+
@option.(:remote, "-r, --remote", "list remote tags")
|
1182
|
+
def list(remote: false)
|
1183
|
+
if remote
|
1184
|
+
#git "show-ref", "--tags"
|
1185
|
+
git "ls-remote", "--tags"
|
1186
|
+
else
|
1187
|
+
git "tag", "-l"
|
1188
|
+
end
|
1189
|
+
end
|
1190
|
+
|
1191
|
+
@action.("create a new tag", important: true)
|
1192
|
+
#@option.(:on, "--on=<commit>", "commit-id where new tag created on")
|
1193
|
+
def create(tag, commit=nil)
|
1194
|
+
args = commit ? [commit] : []
|
1195
|
+
git "tag", tag, *args
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
@action.("delete a tag")
|
1199
|
+
@option.(:remote, "-r, --remote[=origin]", "delete from remote repository")
|
1200
|
+
def delete(tag, *tag_, remote: nil)
|
1201
|
+
tags = [tag] + tag_
|
1202
|
+
if remote
|
1203
|
+
remote = "origin" if remote == true
|
1204
|
+
tags.each do |tag|
|
1205
|
+
#git "push", "--delete", remote, tag # may delete same name branch
|
1206
|
+
git "push", remote, ":refs/tags/#{tag}" # delete a tag safely
|
1207
|
+
end
|
1208
|
+
else
|
1209
|
+
git "tag", "-d", *tags
|
1210
|
+
end
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
@action.("upload tags")
|
1214
|
+
def upload()
|
1215
|
+
git "push", "--tags"
|
1216
|
+
end
|
1217
|
+
|
1218
|
+
@action.("download tags")
|
1219
|
+
def download()
|
1220
|
+
git "fetch", "--tags", "--prune-tags"
|
1221
|
+
end
|
1222
|
+
|
1223
|
+
end
|
1224
|
+
|
1225
|
+
define_alias("tags", "tag:list")
|
1226
|
+
|
1227
|
+
|
1228
|
+
##
|
1229
|
+
## sync:
|
1230
|
+
##
|
1231
|
+
category "sync:" do
|
1232
|
+
|
1233
|
+
uploadopts = optionset {
|
1234
|
+
@option.(:upstream, "-u <remote>" , "set upstream")
|
1235
|
+
@option.(:origin , "-U" , "same as '-u origin'")
|
1236
|
+
@option.(:force , "-f, --force" , "upload forcedly")
|
1237
|
+
}
|
1238
|
+
|
1239
|
+
@action.("download and upload commits")
|
1240
|
+
@optionset.(uploadopts)
|
1241
|
+
def both(upstream: nil, origin: false, force: false)
|
1242
|
+
run_action "pull"
|
1243
|
+
run_action "push", upstream: upstream, origin: origin, force: force
|
1244
|
+
end
|
1245
|
+
|
1246
|
+
@action.("upload commits to remote")
|
1247
|
+
@optionset.(uploadopts)
|
1248
|
+
def push(upstream: nil, origin: false, force: false)
|
1249
|
+
branch = curr_branch()
|
1250
|
+
upstream ||= "origin" if origin
|
1251
|
+
upstream ||= _ask_remote_repo(branch)
|
1252
|
+
#
|
1253
|
+
opts = []
|
1254
|
+
opts << "-f" if force
|
1255
|
+
if upstream
|
1256
|
+
git "push", *opts, "-u", upstream, branch # branch name is required
|
1257
|
+
else
|
1258
|
+
git "push", *opts
|
1259
|
+
end
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
def _ask_remote_repo(branch)
|
1263
|
+
output = `git config --get-regexp '^branch\.'`
|
1264
|
+
has_upstream = output.each_line.any? {|line|
|
1265
|
+
line =~ /\Abranch\.(.*)\.remote / && $1 == branch
|
1266
|
+
}
|
1267
|
+
return nil if has_upstream
|
1268
|
+
remote = ask_to_user "Enter the remote repo name (default: \e[1morigin\e[0m) :"
|
1269
|
+
return remote && ! remote.empty? ? remote : "origin"
|
1270
|
+
end
|
1271
|
+
private :_ask_remote_repo
|
1272
|
+
|
1273
|
+
@action.("download commits from remote and apply them to local")
|
1274
|
+
@option.(:apply, "-N, --not-apply", "just download, not apply", value: false)
|
1275
|
+
def pull(apply: true)
|
1276
|
+
if apply
|
1277
|
+
git "pull", "--prune"
|
1278
|
+
else
|
1279
|
+
git "fetch", "--prune"
|
1280
|
+
end
|
1281
|
+
end
|
1282
|
+
|
1283
|
+
end
|
1284
|
+
|
1285
|
+
define_alias("sync" , "sync:both")
|
1286
|
+
define_alias("push" , "sync:push")
|
1287
|
+
define_alias("upload" , "sync:push")
|
1288
|
+
define_alias("up" , "sync:push")
|
1289
|
+
define_alias("pull" , "sync:pull")
|
1290
|
+
define_alias("download" , "sync:pull")
|
1291
|
+
define_alias("dl" , "sync:pull")
|
1292
|
+
|
1293
|
+
|
1294
|
+
##
|
1295
|
+
## stash:
|
1296
|
+
##
|
1297
|
+
category "stash:" do
|
1298
|
+
|
1299
|
+
@action.("list stash history")
|
1300
|
+
def list()
|
1301
|
+
git "stash", "list"
|
1302
|
+
end
|
1303
|
+
|
1304
|
+
@action.("show changes on stash")
|
1305
|
+
@option.(:num, "-n <N>", "show N-th changes on stash (1-origin)", type: Integer)
|
1306
|
+
#@option.(:index, "-x, --index=<N>", "show N-th changes on stash (0-origin)", type: Integer)
|
1307
|
+
def show(num: nil)
|
1308
|
+
args = num ? ["stash@{#{num - 1}}"] : []
|
1309
|
+
git "stash", "show", "-p", *args
|
1310
|
+
end
|
1311
|
+
|
1312
|
+
@action.("save current changes into stash", important: true)
|
1313
|
+
@option.(:message, "-m <message>", "message")
|
1314
|
+
@option.(:pick, "-p, --pick" , "pick up changes interactively")
|
1315
|
+
def put(*path, message: nil, pick: false)
|
1316
|
+
opts = []
|
1317
|
+
opts << "-m" << message if message
|
1318
|
+
opts << "-p" if pick
|
1319
|
+
args = path.empty? ? [] : ["--"] + path
|
1320
|
+
git "stash", "push", *opts, *args
|
1321
|
+
end
|
1322
|
+
|
1323
|
+
@action.("restore latest changes from stash", important: true)
|
1324
|
+
@option.(:num, "-n <N>", "pop N-th changes on stash (1-origin)", type: Integer)
|
1325
|
+
def pop(num: nil)
|
1326
|
+
args = num ? ["stash@{#{num - 1}}"] : []
|
1327
|
+
git "stash", "pop", *args
|
1328
|
+
end
|
1329
|
+
|
1330
|
+
@action.("delete latest changes from stash")
|
1331
|
+
@option.(:num, "-n, --num=<N>", "drop N-th changes on stash (1-origin)", type: Integer)
|
1332
|
+
def drop(num: nil)
|
1333
|
+
args = num ? ["stash@{#{num - 1}}"] : []
|
1334
|
+
git "stash", "drop", *args
|
1335
|
+
end
|
1336
|
+
|
1337
|
+
end
|
1338
|
+
|
1339
|
+
|
1340
|
+
##
|
1341
|
+
## config:
|
1342
|
+
##
|
1343
|
+
category "config:", action: "handle" do
|
1344
|
+
|
1345
|
+
optset = optionset() {
|
1346
|
+
@option.(:global, "-g, --global", "handle global config")
|
1347
|
+
@option.(:local , "-l, --local" , "handle repository local config")
|
1348
|
+
}
|
1349
|
+
|
1350
|
+
def _build_config_options(global, local)
|
1351
|
+
opts = []
|
1352
|
+
opts << "--global" if global
|
1353
|
+
opts << "--local" if local
|
1354
|
+
return opts
|
1355
|
+
end
|
1356
|
+
|
1357
|
+
@action.("list/get/set/delete config values", usage: [
|
1358
|
+
" # list",
|
1359
|
+
"<key> # get",
|
1360
|
+
"<key> <value> # set",
|
1361
|
+
"<key> \"\" # delete",
|
1362
|
+
"<prefix> # filter by prefix",
|
1363
|
+
], postamble: {
|
1364
|
+
"Example:" => (<<~END).gsub(/^/, " "),
|
1365
|
+
$ gi config # list
|
1366
|
+
$ gi config core.editor # get
|
1367
|
+
$ gi config core.editor vim # set
|
1368
|
+
$ gi config core.editor "" # delete
|
1369
|
+
$ gi config core. # filter by prefix
|
1370
|
+
$ gi config . # list top level prefixes
|
1371
|
+
END
|
1372
|
+
})
|
1373
|
+
@optionset.(optset)
|
1374
|
+
def handle(key=nil, value=nil, global: false, local: false)
|
1375
|
+
opts = _build_config_options(global, local)
|
1376
|
+
if key == nil # list
|
1377
|
+
git "config", *opts, "--list"
|
1378
|
+
elsif value == nil # get or filter
|
1379
|
+
case key
|
1380
|
+
when "." # list top level prefixes
|
1381
|
+
echoback "gi config | awk -F. 'NR>1{d[$1]++}END{for(k in d){print(k\"\\t(\"d[k]\")\")}}' | sort"
|
1382
|
+
d = {}
|
1383
|
+
`git config -l #{opts.join(' ')}`.each_line do |line|
|
1384
|
+
d[$1] = (d[$1] || 0) + 1 if line =~ /^(\w+\.)/
|
1385
|
+
end
|
1386
|
+
d.keys.sort.each {|k| puts "#{k}\t(#{d[k]})" }
|
1387
|
+
when /\.$/ # list (filter)
|
1388
|
+
pat = "^"+key.gsub('.', '\\.')
|
1389
|
+
#git "config", *opts, "--get-regexp", pat # different with `config -l`
|
1390
|
+
echoback "git config -l #{opts.join(' ')} | grep '#{pat}'"
|
1391
|
+
`git config -l #{opts.join(' ')}`.each_line do |line|
|
1392
|
+
print line if line.start_with?(key)
|
1393
|
+
end
|
1394
|
+
else # get
|
1395
|
+
#git "config", "--get", *opts, key
|
1396
|
+
git "config", *opts, key
|
1397
|
+
end
|
1398
|
+
elsif value == "" # delete
|
1399
|
+
git "config", *opts, "--unset", key
|
1400
|
+
else # set
|
1401
|
+
git "config", *opts, key, value
|
1402
|
+
end
|
1403
|
+
end
|
1404
|
+
|
1405
|
+
@action.("set user name and email", usage: [
|
1406
|
+
"<user> <u@email> # set user name and email",
|
1407
|
+
"<user@email> # set email (contains '@')",
|
1408
|
+
"<user> # set user (not contain '@')",
|
1409
|
+
])
|
1410
|
+
@optionset.(optset)
|
1411
|
+
def setuser(user, email=nil, global: false, local: false)
|
1412
|
+
opts = _build_config_options(global, local)
|
1413
|
+
if email == nil && user =~ /@/
|
1414
|
+
email = user
|
1415
|
+
user = nil
|
1416
|
+
end
|
1417
|
+
user = nil if user == '-'
|
1418
|
+
email = nil if email == '-'
|
1419
|
+
git "config", *opts, "user.name" , user if user
|
1420
|
+
git "config", *opts, "user.email", email if email
|
1421
|
+
end
|
1422
|
+
|
1423
|
+
@action.("list/get/set/delete aliases of 'git' (not of 'gi')", usage: [
|
1424
|
+
" # list",
|
1425
|
+
"<name> # get",
|
1426
|
+
"<name> <value> # set",
|
1427
|
+
"<name> \"\" # delete",
|
1428
|
+
])
|
1429
|
+
def alias(name=nil, value=nil)
|
1430
|
+
if value == "" # delete
|
1431
|
+
git "config", "--global", "--unset", "alias.#{name}"
|
1432
|
+
elsif value != nil # set
|
1433
|
+
git "config", "--global", "alias.#{name}", value
|
1434
|
+
elsif name != nil # get
|
1435
|
+
git "config", "--global", "alias.#{name}"
|
1436
|
+
else # list
|
1437
|
+
command = "git config --get-regexp '^alias\\.' | sed -e 's/^alias\\.//;s/ /\\t= /'"
|
1438
|
+
echoback(command)
|
1439
|
+
output = `git config --get-regexp '^alias.'`
|
1440
|
+
print output.gsub(/^alias\.(\S+) (.*)/) { "%s\t= %s" % [$1, $2] }
|
1441
|
+
end
|
1442
|
+
end
|
1443
|
+
|
1444
|
+
|
1445
|
+
end
|
1446
|
+
|
1447
|
+
|
1448
|
+
##
|
1449
|
+
## misc:
|
1450
|
+
##
|
1451
|
+
category "misc:" do
|
1452
|
+
|
1453
|
+
@action.("generate a init file, or print to stdout if no args",
|
1454
|
+
usage: [
|
1455
|
+
"<filename> # generate a file",
|
1456
|
+
" # print to stdout",
|
1457
|
+
])
|
1458
|
+
def initfile(filename=nil)
|
1459
|
+
str = File.read(__FILE__, encoding: "utf-8")
|
1460
|
+
code = str.split(/^__END__\n/, 2)[1]
|
1461
|
+
code = code.gsub(/%SCRIPT%/, APP_CONFIG.app_command)
|
1462
|
+
code = code.gsub(/%ENVVAR_INITFILE%/, ENVVAR_INITFILE)
|
1463
|
+
#
|
1464
|
+
if ! filename || filename == "-"
|
1465
|
+
print code
|
1466
|
+
elsif File.exist?(filename)
|
1467
|
+
raise action_error("#{filename}: File already exists (remove it before generating new file).")
|
1468
|
+
else
|
1469
|
+
File.write(filename, code, encoding: 'utf-8')
|
1470
|
+
puts "[OK] #{filename} generated." unless $QUIET_MODE
|
1471
|
+
end
|
1472
|
+
end
|
1473
|
+
|
1474
|
+
end
|
1475
|
+
|
1476
|
+
|
1477
|
+
end
|
1478
|
+
|
1479
|
+
|
1480
|
+
Benry::CmdApp.module_eval do
|
1481
|
+
define_abbrev("b:" , "branch:")
|
1482
|
+
define_abbrev("c:" , "commit:")
|
1483
|
+
define_abbrev("C:" , "config:")
|
1484
|
+
define_abbrev("g:" , "staging:")
|
1485
|
+
define_abbrev("f:" , "file:")
|
1486
|
+
define_abbrev("r:" , "repo:")
|
1487
|
+
define_abbrev("r:r:", "repo:remote:")
|
1488
|
+
define_abbrev("h:" , "history:")
|
1489
|
+
define_abbrev("h:e:", "history:edit:")
|
1490
|
+
define_abbrev("histedit:", "history:edit:")
|
1491
|
+
#define_abbrev("t:" , "tag:")
|
1492
|
+
#define_abbrev("s:" , "status:")
|
1493
|
+
#define_abbrev("y:" , "sync:")
|
1494
|
+
#define_abbrev("T:" , "stash:")
|
1495
|
+
end
|
1496
|
+
|
1497
|
+
|
1498
|
+
class AppHelpBuilder < Benry::CmdApp::ApplicationHelpBuilder
|
1499
|
+
|
1500
|
+
def build_help_message(*args, **kwargs)
|
1501
|
+
@_omit_actions_part = true
|
1502
|
+
return super
|
1503
|
+
ensure
|
1504
|
+
@_omit_actions_part = false
|
1505
|
+
end
|
1506
|
+
|
1507
|
+
def section_actions(*args, **kwargs)
|
1508
|
+
if @_omit_actions_part
|
1509
|
+
text =" (Too long to show. Run `#{@config.app_command} -l` to list all actions.)"
|
1510
|
+
return render_section(header(:HEADER_ACTIONS), text)
|
1511
|
+
else
|
1512
|
+
return super
|
1513
|
+
end
|
1514
|
+
end
|
1515
|
+
|
1516
|
+
end
|
1517
|
+
|
1518
|
+
|
1519
|
+
def self.main(argv=ARGV)
|
1520
|
+
errmsg = _load_init_file(ENV[ENVVAR_INITFILE])
|
1521
|
+
if errmsg
|
1522
|
+
$stderr.puts "\e[31m[ERROR]\e[0m #{errmsg}"
|
1523
|
+
return 1
|
1524
|
+
end
|
1525
|
+
#
|
1526
|
+
APP_CONFIG.default_action = GIT_CONFIG.default_action
|
1527
|
+
app_help_builder = AppHelpBuilder.new(APP_CONFIG)
|
1528
|
+
app = Benry::CmdApp::Application.new(APP_CONFIG, nil, app_help_builder)
|
1529
|
+
return app.main(argv)
|
1530
|
+
end
|
1531
|
+
|
1532
|
+
def self._load_init_file(filename)
|
1533
|
+
return nil if filename == nil || filename.empty?
|
1534
|
+
filename = File.expand_path(filename)
|
1535
|
+
File.exist?(filename) or
|
1536
|
+
return "#{filename}: Init file specified but not exist."
|
1537
|
+
require File.absolute_path(filename)
|
1538
|
+
return nil
|
1539
|
+
end
|
1540
|
+
private_class_method :_load_init_file
|
1541
|
+
|
1542
|
+
|
1543
|
+
end
|
1544
|
+
|
1545
|
+
|
1546
|
+
if __FILE__ == $0
|
1547
|
+
exit GitImproved.main()
|
1548
|
+
end
|
1549
|
+
|
1550
|
+
|
1551
|
+
__END__
|
1552
|
+
# coding: utf-8
|
1553
|
+
# frozen_string_literal: true
|
1554
|
+
|
1555
|
+
##
|
1556
|
+
## @(#) Init file for '%SCRIPT%' command.
|
1557
|
+
##
|
1558
|
+
## This file is loaded by '%SCRIPT%' command only if $%ENVVAR_INITFILE% is set,
|
1559
|
+
## for example:
|
1560
|
+
##
|
1561
|
+
## $ gi hello
|
1562
|
+
## [ERROR] hello: Action not found.
|
1563
|
+
##
|
1564
|
+
## $ export %ENVVAR_INITFILE%="~/.gi_init.rb"
|
1565
|
+
## $ gi hello
|
1566
|
+
## Hello, world!
|
1567
|
+
##
|
1568
|
+
|
1569
|
+
GitImproved.module_eval do
|
1570
|
+
|
1571
|
+
|
1572
|
+
##
|
1573
|
+
## Configuration example
|
1574
|
+
##
|
1575
|
+
GIT_CONFIG.tap do |c|
|
1576
|
+
#c.prompt = "[gi]$ "
|
1577
|
+
#c.default_action = "status:here" # or: "status:info"
|
1578
|
+
#c.initial_branch = "main" # != 'master'
|
1579
|
+
#c.initial_commit_message = "Initial commit (empty)"
|
1580
|
+
#c.gitignore_items = ["*~", "*.DS_Store", "tmp/*", "*.pyc"]
|
1581
|
+
#c.history_graph_format = "%C(auto)%h %ad <%al> | %d %s"
|
1582
|
+
##c.history_graph_format = "\e[32m%h %ad\e[0m <%al> \e[2m|\e[0m\e[33m%d\e[0m %s"
|
1583
|
+
#c.history_graph_options = ["--graph", "--date=short", "--decorate"]
|
1584
|
+
end
|
1585
|
+
|
1586
|
+
|
1587
|
+
##
|
1588
|
+
## Custom alias example
|
1589
|
+
##
|
1590
|
+
GitAction.class_eval do
|
1591
|
+
|
1592
|
+
## `gi br <branch>` == `gi breanch:create -w <branch>`
|
1593
|
+
define_alias "br", ["branch:create", "-w"]
|
1594
|
+
|
1595
|
+
end
|
1596
|
+
|
1597
|
+
|
1598
|
+
##
|
1599
|
+
## Custom action example
|
1600
|
+
##
|
1601
|
+
GitAction.class_eval do
|
1602
|
+
|
1603
|
+
#category "example:" do
|
1604
|
+
|
1605
|
+
langs = ["en", "fr", "it"]
|
1606
|
+
|
1607
|
+
@action.("print greeting message")
|
1608
|
+
@option.(:lang, "-l, --lang=<lang>", "language (en/fr/it)", enum: langs)
|
1609
|
+
def hello(name="world", lang: "en")
|
1610
|
+
case lang
|
1611
|
+
when "en" ; puts "Hello, #{name}!"
|
1612
|
+
when "fr" ; puts "Bonjour, #{name}!"
|
1613
|
+
when "it" ; puts "Chao, #{name}!"
|
1614
|
+
else
|
1615
|
+
raise option_error("#{lang}: Unknown language.")
|
1616
|
+
end
|
1617
|
+
end
|
1618
|
+
|
1619
|
+
#end
|
1620
|
+
|
1621
|
+
#define_alias "hello", "example:hello"
|
1622
|
+
|
1623
|
+
end
|
1624
|
+
|
1625
|
+
|
1626
|
+
end
|