git-improved 0.1.0
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.
- 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
|