abak-flow 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.
- data/.gitignore +6 -0
- data/Gemfile +6 -0
- data/README.md +34 -0
- data/Rakefile +1 -0
- data/VERSION +1 -0
- data/abak-flow.gemspec +23 -0
- data/bin/request +4 -0
- data/lib/abak-flow.rb +4 -0
- data/lib/abak-flow/hub_extensions.rb +114 -0
- data/lib/abak-flow/request.rb +123 -0
- data/lib/abak-flow/version.rb +5 -0
- metadata +80 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
Abak-flow
|
2
|
+
=========
|
3
|
+
Нет, это не новая идеология ведения проекта, это всего лишь набор утилит которые помогают связать использование git-flow и github
|
4
|
+
|
5
|
+
# Концепция
|
6
|
+
Идеология git-flow использует слудующий набор веток:
|
7
|
+
|
8
|
+
* *master* - всегда пригодна для развертывания
|
9
|
+
* *develop* - основная ветка разработки
|
10
|
+
* *hotfix* - ветка для изменений которые попадут на продакшен сервер
|
11
|
+
* *feature* - ветки для крупных задачь
|
12
|
+
|
13
|
+
Github-flow же наоборот ведет основную разработку в ветке master, но при этом master является пригодным для развертывания в любой момент.
|
14
|
+
|
15
|
+
После долгих раздумий было принято применить следующий набор правил, для разработки на github:
|
16
|
+
|
17
|
+
1. Вся разработка любой задачи и функционала ведется только в ветках **feature**
|
18
|
+
2. Разработаный функционал из ветки **feature** оформляется pull request только в ветку **develop**
|
19
|
+
3. Все исправления ошибок, которые должны попасть на продакшен сервер делаются только в ветках **hotfix**
|
20
|
+
4. Исправленные ошибки из ветки **hotfix** фофрмляются pull request только в ветку **master**
|
21
|
+
5. После получения исправлений на текущий момент в репозитории инициируется merge ветки **master** в **develop**
|
22
|
+
|
23
|
+
|
24
|
+
# Установка
|
25
|
+
*в процессе*
|
26
|
+
|
27
|
+
# С чего начать?
|
28
|
+
$ git request help
|
29
|
+
|
30
|
+
# Примеры использования
|
31
|
+
*в процессе*
|
32
|
+
|
33
|
+
# В заключении
|
34
|
+
Данный репозиторий и изложенные в нем идеи ни в коем случае не претендуют на идеал и совершенство. Это всего лишь узко заточенная комбинация гемов
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/abak-flow.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'abak-flow/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'abak-flow'
|
7
|
+
s.version = Abak::Flow::VERSION
|
8
|
+
s.authors = ['Strech (aka Sergey Fedorov)']
|
9
|
+
s.email = ['oni.strech@gmail.com']
|
10
|
+
s.homepage = 'https://github.com/Strech/abak-flow'
|
11
|
+
s.summary = %q{Совмещение 2-х подходов разработки Git-flow & Github-flow}
|
12
|
+
s.description = %q{Простой набор правил и комманд, заточеных для работы в git-flow с использование в качестве удаленного репозитория github}
|
13
|
+
|
14
|
+
s.rubyforge_project = 'abak-flow'
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ['lib']
|
20
|
+
|
21
|
+
s.add_runtime_dependency 'hub'
|
22
|
+
s.add_runtime_dependency 'commander'
|
23
|
+
end
|
data/bin/request
ADDED
data/lib/abak-flow.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
module Abak::Flow
|
2
|
+
module RunnerExtension
|
3
|
+
def execute
|
4
|
+
if args.noop?
|
5
|
+
puts commands
|
6
|
+
elsif not args.skip?
|
7
|
+
if args.chained?
|
8
|
+
execute_command_chain
|
9
|
+
else
|
10
|
+
%x{#{args.to_exec.join(' ')}}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module CommandsExtension
|
17
|
+
def pull_request(args)
|
18
|
+
args.shift
|
19
|
+
options = { }
|
20
|
+
force = explicit_owner = false
|
21
|
+
base_project = local_repo.main_project
|
22
|
+
head_project = local_repo.current_project
|
23
|
+
|
24
|
+
from_github_ref = lambda do |ref, context_project|
|
25
|
+
if ref.index(':')
|
26
|
+
owner, ref = ref.split(':', 2)
|
27
|
+
project = github_project(context_project.name, owner)
|
28
|
+
end
|
29
|
+
[project || context_project, ref]
|
30
|
+
end
|
31
|
+
|
32
|
+
while arg = args.shift
|
33
|
+
case arg
|
34
|
+
when '-f'
|
35
|
+
force = true
|
36
|
+
when '-b'
|
37
|
+
base_project, options[:base] = from_github_ref.call(args.shift, base_project)
|
38
|
+
when '-h'
|
39
|
+
head = args.shift
|
40
|
+
explicit_owner = !!head.index(':')
|
41
|
+
head_project, options[:head] = from_github_ref.call(head, head_project)
|
42
|
+
when '-i'
|
43
|
+
options[:issue] = args.shift
|
44
|
+
when '-d'
|
45
|
+
options[:body] = args.shift
|
46
|
+
else
|
47
|
+
if url = resolve_github_url(arg) and url.project_path =~ /^issues\/(\d+)/
|
48
|
+
options[:issue] = $1
|
49
|
+
base_project = url.project
|
50
|
+
elsif !options[:title] then options[:title] = arg
|
51
|
+
else
|
52
|
+
abort "invalid argument: #{arg}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
options[:project] = base_project
|
58
|
+
options[:base] ||= master_branch.short_name
|
59
|
+
|
60
|
+
if tracked_branch = options[:head].nil? && current_branch.upstream
|
61
|
+
if base_project == head_project and tracked_branch.short_name == options[:base]
|
62
|
+
$stderr.puts "Aborted: head branch is the same as base (#{options[:base].inspect})"
|
63
|
+
warn "(use `-h <branch>` to specify an explicit pull request head)"
|
64
|
+
abort
|
65
|
+
end
|
66
|
+
end
|
67
|
+
options[:head] ||= (tracked_branch || current_branch).short_name
|
68
|
+
|
69
|
+
# when no tracking, assume remote branch is published under active user's fork
|
70
|
+
user = github_user(true, head_project.host)
|
71
|
+
if head_project.owner != user and !tracked_branch and !explicit_owner
|
72
|
+
head_project = head_project.owned_by(user)
|
73
|
+
end
|
74
|
+
|
75
|
+
remote_branch = "#{head_project.remote}/#{options[:head]}"
|
76
|
+
options[:head] = "#{head_project.owner}:#{options[:head]}"
|
77
|
+
|
78
|
+
if !force and tracked_branch and local_commits = git_command("rev-list --cherry #{remote_branch}...")
|
79
|
+
$stderr.puts "Aborted: #{local_commits.split("\n").size} commits are not yet pushed to #{remote_branch}"
|
80
|
+
warn "(use `-f` to force submit a pull request anyway)"
|
81
|
+
abort
|
82
|
+
end
|
83
|
+
|
84
|
+
if args.noop?
|
85
|
+
puts "Would reqest a pull to #{base_project.owner}:#{options[:base]} from #{options[:head]}"
|
86
|
+
exit
|
87
|
+
end
|
88
|
+
|
89
|
+
unless options[:title] or options[:issue]
|
90
|
+
base_branch = "#{base_project.remote}/#{options[:base]}"
|
91
|
+
changes = git_command "log --no-color --pretty=medium --cherry %s...%s" %
|
92
|
+
[base_branch, remote_branch]
|
93
|
+
|
94
|
+
options[:title], options[:body] = pullrequest_editmsg(changes) { |msg|
|
95
|
+
msg.puts "# Requesting a pull to #{base_project.owner}:#{options[:base]} from #{options[:head]}"
|
96
|
+
msg.puts "#"
|
97
|
+
msg.puts "# Write a message for this pull request. The first block"
|
98
|
+
msg.puts "# of text is the title and the rest is description."
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
pull = create_pullrequest(options)
|
103
|
+
|
104
|
+
args.executable = 'echo'
|
105
|
+
args.replace [pull['html_url']]
|
106
|
+
rescue HTTPExceptions
|
107
|
+
display_http_exception("creating pull request", $!.response)
|
108
|
+
exit 1
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
Hub::Runner.send :include, RunnerExtension
|
113
|
+
Hub::Commands.send :include, CommandsExtension
|
114
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Abak::Flow
|
3
|
+
# @TODO Сделать класс, в котором собрать общие куски из задач
|
4
|
+
|
5
|
+
program :name, 'Утилита для оформления pull request на github.com'
|
6
|
+
program :version, '0.0.1'
|
7
|
+
program :description, 'Утилита, заточенная под git-flow но с использованием github.com'
|
8
|
+
|
9
|
+
default_command :help
|
10
|
+
command :publish do |c|
|
11
|
+
c.syntax = 'git request publish <Заголовок>'
|
12
|
+
c.description = 'Оформить pull request из текущей ветки (feature -> develop, hotfix -> master)'
|
13
|
+
|
14
|
+
# Опции нужны, если человек хочет запушить ветку, с именем отличным от стандарта
|
15
|
+
c.option '--head STRING', String, 'Имя ветки, которую нужно принять в качестве изменений'
|
16
|
+
c.option '--base STRING', String, 'Имя ветки, в которую нужно принять изменения'
|
17
|
+
|
18
|
+
c.action do |args, options|
|
19
|
+
repository = Hub::Commands.send :local_repo
|
20
|
+
current_branch = repository.current_branch.short_name
|
21
|
+
request_rules = {
|
22
|
+
:feature => :develop,
|
23
|
+
:hotfix => :master
|
24
|
+
}
|
25
|
+
jira_browse_url = 'http://jira.dev.apress.ru/browse/'
|
26
|
+
|
27
|
+
# Проверим, что мы не в мастере или девелопе
|
28
|
+
if [:master, :develop].include? current_branch.to_sym
|
29
|
+
say 'Нельзя делать pull request из меток master или develop'
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
|
33
|
+
# Проверим, что у нас настроен upstream
|
34
|
+
if repository.remote_by_name('upstream').nil?
|
35
|
+
say 'Необходимо настроить репозиторий upstream (главный) для текущего пользователя'
|
36
|
+
say '=> git remote add upstream https://Developer@github.com/abak-press/sample.git'
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
|
40
|
+
if args.first.to_s.empty?
|
41
|
+
say 'Пожалуйста, укажите в заголовке номер вашей задачи, например так:'
|
42
|
+
say '=> git request "PC-001"'
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
|
46
|
+
# Расставим ветки согласно правилам
|
47
|
+
remote_branch, task = current_branch.split('/').push(nil).map(&:to_s)
|
48
|
+
head = "#{repository.repo_owner}:#{current_branch}"
|
49
|
+
base = "#{repository.remote_by_name('upstream').project.owner}:#{request_rules.fetch(remote_branch.to_sym, '')}"
|
50
|
+
|
51
|
+
head = options.head unless options.head.nil?
|
52
|
+
base = options.base unless options.base.nil?
|
53
|
+
|
54
|
+
# Запушим текущую ветку на origin
|
55
|
+
# @TODO Может быть лучше достать дерективу конфига origin?
|
56
|
+
say "=> Обновляю ветку #{current_branch} на origin"
|
57
|
+
Hub::Runner.execute('push', repository.main_project.remote.name, current_branch)
|
58
|
+
|
59
|
+
# Запостим pull request на upstream
|
60
|
+
command_options = ['pull-request', args.first, '-b', base, '-h', head, '-d']
|
61
|
+
command_options |= ['-d', jira_browse_url + task] if task =~ /^\w+\-\d{1,}$/
|
62
|
+
|
63
|
+
say '=> Делаю pull request на upstream'
|
64
|
+
say Hub::Runner.execute(*command_options)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
command :update do |c|
|
69
|
+
c.syntax = 'git request update'
|
70
|
+
c.description = 'Обновить ветку на удаленном (origin) репозитории'
|
71
|
+
|
72
|
+
c.option '--branch STRING', String, 'Имя ветки, которую нужно обновить'
|
73
|
+
|
74
|
+
c.action do |args, options|
|
75
|
+
repository = Hub::Commands.send :local_repo
|
76
|
+
current_branch = repository.current_branch.short_name
|
77
|
+
|
78
|
+
# Запушим текущую ветку на origin
|
79
|
+
branch = options.branch || current_branch
|
80
|
+
say "=> Обновляю ветку #{branch} на origin"
|
81
|
+
Hub::Runner.execute('push', repository.main_project.remote.name, branch)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
command :done do |c|
|
86
|
+
c.syntax = 'git request done'
|
87
|
+
c.description = 'Завершить pull request. По умолчанию удаляются ветки как локальная (local), так и удаленная (origin)'
|
88
|
+
|
89
|
+
c.option '--branch STRING', String, 'Имя ветки pull request которой нужно закрыть'
|
90
|
+
c.option '--all', 'Удаляет ветку в локальном репозитории и в удалнном (local + origin) (по умолчанию)'
|
91
|
+
c.option '--local', 'Удаляет ветку только в локальном репозитории (local)'
|
92
|
+
c.option '--origin', 'Удаляет ветку в удаленном репозитории (origin)'
|
93
|
+
|
94
|
+
c.action do |args, options|
|
95
|
+
repository = Hub::Commands.send :local_repo
|
96
|
+
current_branch = repository.current_branch.short_name
|
97
|
+
branch = options.branch || current_branch
|
98
|
+
|
99
|
+
type = :all
|
100
|
+
if [options.local, options.origin].compact.count == 1
|
101
|
+
type = options.local ? :local : :origin
|
102
|
+
end
|
103
|
+
|
104
|
+
warning = "Внимание! Alarm! Danger! Achtung\nЕсли вы удалите ветку на удаленном репозитории, а ваш pull request еще не приняли, вы рискуете потерять проделанную работу.\nВы уверены, что хотите продолжить?"
|
105
|
+
if [:all, :origin].include?(type)
|
106
|
+
say '=> Вы приняли верное решение :)' && exit unless agree("#{warning} [yes/no/y/n]:")
|
107
|
+
end
|
108
|
+
|
109
|
+
if [:all, :origin].include? type
|
110
|
+
say "=> Удаляю ветку #{branch} на origin"
|
111
|
+
Hub::Runner.execute('push', repository.main_project.remote.name, ':' + branch)
|
112
|
+
end
|
113
|
+
|
114
|
+
if [:all, :local].include? type
|
115
|
+
remote_branch, task = current_branch.split('/').push(nil).map(&:to_s)
|
116
|
+
|
117
|
+
say "=> Удаляю локальную ветку #{branch}"
|
118
|
+
Hub::Runner.execute('checkout', 'develop')
|
119
|
+
Hub::Runner.execute('branch', '-D', branch)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: abak-flow
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Strech (aka Sergey Fedorov)
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-04 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: hub
|
16
|
+
requirement: &70279720232900 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70279720232900
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: commander
|
27
|
+
requirement: &70279720232480 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70279720232480
|
36
|
+
description: Простой набор правил и комманд, заточеных для работы в git-flow с использование
|
37
|
+
в качестве удаленного репозитория github
|
38
|
+
email:
|
39
|
+
- oni.strech@gmail.com
|
40
|
+
executables:
|
41
|
+
- request
|
42
|
+
extensions: []
|
43
|
+
extra_rdoc_files: []
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- Gemfile
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- VERSION
|
50
|
+
- abak-flow.gemspec
|
51
|
+
- bin/request
|
52
|
+
- lib/abak-flow.rb
|
53
|
+
- lib/abak-flow/hub_extensions.rb
|
54
|
+
- lib/abak-flow/request.rb
|
55
|
+
- lib/abak-flow/version.rb
|
56
|
+
homepage: https://github.com/Strech/abak-flow
|
57
|
+
licenses: []
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project: abak-flow
|
76
|
+
rubygems_version: 1.8.10
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Совмещение 2-х подходов разработки Git-flow & Github-flow
|
80
|
+
test_files: []
|