abak-flow 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|