gitty 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.textile +21 -0
- data/Rakefile +29 -0
- data/assets/helpers/git-cleanup-branches +151 -0
- data/assets/helpers/git-strip-new-whitespace +30 -0
- data/assets/helpers/git-submodule-helper +262 -0
- data/assets/helpers/git-trash +26 -0
- data/assets/helpers/git-when-introduced +16 -0
- data/assets/helpers/hookd_wrapper +22 -0
- data/assets/hooks/clean-patches +43 -0
- data/assets/hooks/git-post-checkout-submodules +74 -0
- data/assets/hooks/git-prevent-messy-rebase +17 -0
- data/assets/hooks/prevent-nocommit-tags +11 -0
- data/bin/git-hook +4 -0
- data/cucumber.yml +1 -0
- data/lib/ext.rb +16 -0
- data/lib/gitty.rb +55 -0
- data/lib/gitty/commands/add.rb +26 -0
- data/lib/gitty/commands/init.rb +55 -0
- data/lib/gitty/commands/list.rb +67 -0
- data/lib/gitty/commands/manager.rb +50 -0
- data/lib/gitty/commands/publish.rb +34 -0
- data/lib/gitty/commands/remove.rb +17 -0
- data/lib/gitty/commands/shell.rb +32 -0
- data/lib/gitty/helpers.rb +21 -0
- data/lib/gitty/hook.rb +42 -0
- data/lib/gitty/runner.rb +44 -0
- data/lib/string.rb +13 -0
- data/spec/gitty/commands/add_spec.rb +5 -0
- data/spec/gitty_spec.rb +59 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/support/constants.rb +1 -0
- metadata +90 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# 'git trash' does the same thing as 'git reset --hard' or 'git checkout ./',
|
4
|
+
# except rather than completely vaporizing the changes, it stores them in an
|
5
|
+
# unreachable stash object. This means you can save yourself from accidents
|
6
|
+
# without having to grep the filesystem.
|
7
|
+
#
|
8
|
+
# trashed changes can be found using 'git fsck' with relative ease.
|
9
|
+
#
|
10
|
+
# 'git prune' will delete your trashed changes. 'git gc' will delete your
|
11
|
+
# trashed changes older than gc.pruneExpire (which defaults to 2 weeks
|
12
|
+
# according to the git-gc man page).
|
13
|
+
|
14
|
+
current_stash=$(git rev-parse stash@{0})
|
15
|
+
if [ "$1" == "-a" ]; then
|
16
|
+
git add .
|
17
|
+
fi
|
18
|
+
|
19
|
+
result=$(git stash save 'trashed changes' 2>&1)
|
20
|
+
saved_stash=$(git rev-parse stash@{0})
|
21
|
+
if [ ! "$current_stash" == "$saved_stash" ]; then
|
22
|
+
result=$(git stash drop 2>&1)
|
23
|
+
echo "Changes were trashed. Type 'git stash apply $saved_stash to bring them back"
|
24
|
+
else
|
25
|
+
echo "Trash failed. 'git stash' reported: $result" 1>&2
|
26
|
+
fi
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
if [ -z "$1" ]; then
|
3
|
+
echo "Usage: $0 <commit> <branch>"
|
4
|
+
echo " branch is optimal and defaults to current"
|
5
|
+
exit 1
|
6
|
+
fi
|
7
|
+
what_ref=$1
|
8
|
+
branch=$(git symbolic-ref HEAD | sed 's|refs/heads/||')
|
9
|
+
git reflog show $branch@{now} | while read ref rest; do
|
10
|
+
if [ -z "$(git rev-list -1 $ref..$what_ref)" ]; then
|
11
|
+
printf "[x] "
|
12
|
+
else
|
13
|
+
printf "[ ] "
|
14
|
+
fi
|
15
|
+
echo $ref $rest
|
16
|
+
done | less -R
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
dirname=$(dirname $0)
|
3
|
+
hooktype=$(basename $0)
|
4
|
+
|
5
|
+
# Check for updates. Since there is no post-fetch hook, we check before we're about to run a hook.
|
6
|
+
remote_branch=$(git show-ref origin/--hooks--)
|
7
|
+
[ -n "$remote_branch" ] && (
|
8
|
+
cd ${dirname}/shared > /dev/null
|
9
|
+
local_rev=$(GIT_OBJECT_DIRECTORY="../../objects" git rev-parse HEAD)
|
10
|
+
[ "${remote_branch%% *}" != "$local_rev" ] && (
|
11
|
+
echo "Hook updates were applied:" 1>&2
|
12
|
+
GIT_OBJECT_DIRECTORY=../../objects git branch origin/--hooks-- ${remote_branch%% *} -f 1>&2
|
13
|
+
GIT_OBJECT_DIRECTORY=../../objects git reset --hard ${remote_branch%% *} 1>&2
|
14
|
+
)
|
15
|
+
)
|
16
|
+
|
17
|
+
for base_dir in "${dirname}"/{shared,local}; do
|
18
|
+
[ -d "${base_dir}/${hooktype}.d" ] || continue
|
19
|
+
for script in "${base_dir}/${hooktype}.d"/*; do
|
20
|
+
HELPERS="${base_dir}/helpers" "${script}" || exit $?
|
21
|
+
done
|
22
|
+
done
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
#
|
3
|
+
# description: |-
|
4
|
+
# A hook script to verify what is about to be committed.
|
5
|
+
# Called by git-commit with no arguments. The hook should
|
6
|
+
# exit with non-zero status after issuing an appropriate message if
|
7
|
+
# it wants to stop the commit.
|
8
|
+
# targets: ["pre-commit"]
|
9
|
+
|
10
|
+
# If you want to allow non-ascii filenames set this variable to true.
|
11
|
+
allownonascii=$(git config hooks.allownonascii)
|
12
|
+
|
13
|
+
# Cross platform projects tend to avoid non-ascii filenames; prevent
|
14
|
+
# them from being added to the repository. We exploit the fact that the
|
15
|
+
# printable range starts at the space character and ends with tilde.
|
16
|
+
if [ "$allownonascii" != "true" ] &&
|
17
|
+
test "$(git diff --cached --name-only --diff-filter=A -z |
|
18
|
+
LC_ALL=C tr -d '[ -~]\0')"
|
19
|
+
then
|
20
|
+
echo "Error: Attempt to add a non-ascii filename."
|
21
|
+
echo
|
22
|
+
echo "This can cause problems if you want to work together"
|
23
|
+
echo "with people on other platforms than you."
|
24
|
+
echo
|
25
|
+
echo "To be portable it is adviseable to rename the file ..."
|
26
|
+
echo
|
27
|
+
echo "If you know what you are doing you can disable this"
|
28
|
+
echo "check using:"
|
29
|
+
echo
|
30
|
+
echo " git config hooks.allownonascii true"
|
31
|
+
echo
|
32
|
+
exit 1
|
33
|
+
fi
|
34
|
+
|
35
|
+
if git-rev-parse --verify HEAD >/dev/null 2>&1
|
36
|
+
then
|
37
|
+
against=HEAD
|
38
|
+
else
|
39
|
+
# Initial commit: diff against an empty tree object
|
40
|
+
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
|
41
|
+
fi
|
42
|
+
|
43
|
+
exec git diff-index --check --cached $against --
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# description: |-
|
4
|
+
# A git hook that automatically updates your submodules for you when you change branches or pull
|
5
|
+
#
|
6
|
+
# It works like this:
|
7
|
+
# - If before you changed branches, your submodule's revision differed from what the master repo specified, your submodule is left alone
|
8
|
+
# - If a submodule has been removed from the branch you move to, it alerts you
|
9
|
+
# - If a submodule has been added on the branch you move to, it alerts you
|
10
|
+
# - Otherwise, it checks out the revision for you
|
11
|
+
# version: 0.1
|
12
|
+
# targets: ["post-applypatch", "post-checkout", "post-merge"]
|
13
|
+
|
14
|
+
module GitMethods
|
15
|
+
def chdir_parent
|
16
|
+
Dir.chdir('..') until File.directory?('.git') || Dir.pwd == '/'
|
17
|
+
end
|
18
|
+
|
19
|
+
def list_submodules(ref)
|
20
|
+
`git ls-tree --full-tree -r #{ref} | egrep '^160000'`.split("\n").inject({}) do |h, line|
|
21
|
+
info, path = line.split("\t")
|
22
|
+
filemode, object_type, ref = info.split(" ")
|
23
|
+
h[path] = ref
|
24
|
+
h
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def submodule_current_rev(path)
|
29
|
+
return nil unless File.directory?(path)
|
30
|
+
ref = nil
|
31
|
+
Dir.chdir(path) do
|
32
|
+
ref = `git rev-parse HEAD`.chomp
|
33
|
+
end
|
34
|
+
ref
|
35
|
+
end
|
36
|
+
|
37
|
+
def output_submodule_header(path)
|
38
|
+
puts "\nSubmodule: #{path}\n#{'-' * 60}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
include GitMethods
|
42
|
+
|
43
|
+
chdir_parent
|
44
|
+
current_submodules = list_submodules('HEAD')
|
45
|
+
previous_submodules = list_submodules('HEAD@{1}')
|
46
|
+
|
47
|
+
(current_submodules.keys + previous_submodules.keys).uniq.sort.each do |path|
|
48
|
+
rev = submodule_current_rev(path)
|
49
|
+
case
|
50
|
+
when rev.nil?
|
51
|
+
output_submodule_header(path)
|
52
|
+
# it should be initialized / unstashed
|
53
|
+
puts "Submodule is new and needs to be initialized"
|
54
|
+
when rev == current_submodules[path]
|
55
|
+
# do nothing
|
56
|
+
when rev != previous_submodules[path]
|
57
|
+
output_submodule_header(path)
|
58
|
+
puts rev
|
59
|
+
# it was modified before... don't touch it
|
60
|
+
puts "Not updating '#{path}' because it's revision pointer isn't the same as the previous HEAD specified"
|
61
|
+
when current_submodules[path].nil?
|
62
|
+
output_submodule_header(path)
|
63
|
+
# it should be stashed
|
64
|
+
puts "Does not exist in this revision (you may wish to stash it with git submodule-helper stash)."
|
65
|
+
when rev != current_submodules[path]
|
66
|
+
output_submodule_header(path)
|
67
|
+
# it should be updated to the latest
|
68
|
+
# Fetch if it the change doesn't exist
|
69
|
+
Dir.chdir(path) do
|
70
|
+
system("(git show '#{current_submodules[path]}' 2> /dev/null 1> /dev/null) || git fetch")
|
71
|
+
system("git checkout '#{current_submodules[path]}'")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# description: Detects if your rebase will rewrite commits that have been propagated to other branches, and stops the rebase if so.
|
3
|
+
# version: 0.1
|
4
|
+
# targets: ["pre-rebase"]
|
5
|
+
# helpers: []
|
6
|
+
|
7
|
+
target_branch = ARGV[0]
|
8
|
+
latest_commit = %x{git rev-list #{target_branch}..HEAD}.split("\n").last
|
9
|
+
other_branches_with_commit = %x{git branch --contains #{latest_commit}}.split("\n").select { |l| ! /^ *\*/.match(l) }.map {|b| b.strip }
|
10
|
+
if other_branches_with_commit.empty?
|
11
|
+
exit 0
|
12
|
+
else
|
13
|
+
puts "Running this rebase would cause commit #{latest_commit[0..-7]} be rewritten, but it's included in the history of #{other_branches_with_commit * ", "}\n\n\n"
|
14
|
+
system("git show #{latest_commit} --pretty=oneline")
|
15
|
+
puts "\n\n\n"
|
16
|
+
exit 1
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
# description: Let's you tag debug code and prevent it from being committed by adding the word "NOCOMMIT" to your source
|
3
|
+
# version: 0.1
|
4
|
+
# targets: ["pre-commit"]
|
5
|
+
# helpers: []
|
6
|
+
|
7
|
+
if git diff --cached | grep NOCOMMIT > /dev/null; then
|
8
|
+
echo "You tried to commit a line containing NOCOMMIT"
|
9
|
+
exit 1
|
10
|
+
fi
|
11
|
+
exit 0
|
data/bin/git-hook
ADDED
data/cucumber.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
default: -f pretty -r features
|
data/lib/ext.rb
ADDED
data/lib/gitty.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
GITTY_ROOT_PATH = Pathname.new(File.expand_path("..", File.dirname(__FILE__)))
|
4
|
+
ASSETS_PATH = GITTY_ROOT_PATH + "assets"
|
5
|
+
GITTY_LIB_PATH = GITTY_ROOT_PATH + "lib"
|
6
|
+
GITTY_PATH = GITTY_ROOT_PATH + "lib/gitty"
|
7
|
+
$: << GITTY_LIB_PATH.to_s
|
8
|
+
|
9
|
+
require 'fileutils'
|
10
|
+
require 'stringio'
|
11
|
+
require 'yaml'
|
12
|
+
require "string.rb"
|
13
|
+
require "ext.rb"
|
14
|
+
|
15
|
+
module Gitty
|
16
|
+
autoload :Helpers, (GITTY_PATH + "helpers.rb").to_s
|
17
|
+
def self.asset_paths
|
18
|
+
[ENV["GITTY_ASSETS"], ENV["HOME"] + "/.gitty", ASSETS_PATH].compact.map {|p| Pathname.new(p)}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.find_asset(file)
|
22
|
+
asset_paths.each do |asset_path|
|
23
|
+
fullpath = File.join(asset_path, file)
|
24
|
+
return fullpath if File.exist?(fullpath)
|
25
|
+
end
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.creating_dir_if_nonexistant(dir)
|
30
|
+
FileUtils.mkdir_p(dir)
|
31
|
+
Pathname.new(dir)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.extract_meta_data(string_or_io)
|
35
|
+
io = string_or_io.respond_to?(:readline) ? string_or_io : StringIO.new(string_or_io)
|
36
|
+
meta_yaml = ""
|
37
|
+
begin
|
38
|
+
while line = io.readline
|
39
|
+
next unless line.match(/^# (description.+)/)
|
40
|
+
meta_yaml = "#{$1}\n"
|
41
|
+
break
|
42
|
+
end
|
43
|
+
|
44
|
+
while line = io.readline
|
45
|
+
break unless line.match(/^# (.+)/)
|
46
|
+
meta_yaml << "#{$1}\n"
|
47
|
+
end
|
48
|
+
rescue EOFError
|
49
|
+
end
|
50
|
+
meta_yaml.empty? ? nil : YAML.load(meta_yaml)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
require "gitty/runner.rb"
|
55
|
+
require "gitty/hook.rb"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require GITTY_PATH + "commands/manager"
|
2
|
+
|
3
|
+
class Gitty::Hook::Add < Gitty::Hook::Manager
|
4
|
+
include FileUtils
|
5
|
+
|
6
|
+
def run
|
7
|
+
cp(src_hook_file, master_hook_file)
|
8
|
+
chmod(0755, master_hook_file)
|
9
|
+
meta_data["targets"].each do |target|
|
10
|
+
ln_s(
|
11
|
+
"../hooks/#{@hookname}",
|
12
|
+
file_with_existing_directory!(base_directory + "#{target}.d" + @hookname)
|
13
|
+
)
|
14
|
+
end
|
15
|
+
(meta_data["helpers"] || []).each do |helper|
|
16
|
+
cp(Gitty.find_asset("helpers/#{helper}"), helpers_directory + helper)
|
17
|
+
chmod(0755, helpers_directory + helper)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def option_parser
|
22
|
+
@option_parser ||= super.tap do |opts|
|
23
|
+
opts.banner = "Usage: git hook add [opts] hook-name"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
class Gitty::Hook::Init < Gitty::Runner
|
3
|
+
include ::Gitty::Helpers
|
4
|
+
|
5
|
+
CLIENT_HOOKS = %w[
|
6
|
+
applypatch-msg
|
7
|
+
commit-msg
|
8
|
+
post-applypatch
|
9
|
+
post-checkout
|
10
|
+
post-commit
|
11
|
+
post-merge
|
12
|
+
pre-applypatch
|
13
|
+
pre-auto-gc
|
14
|
+
pre-commit
|
15
|
+
pre-rebase
|
16
|
+
prepare-commit-msg
|
17
|
+
]
|
18
|
+
def run
|
19
|
+
puts "Initializing with gitty"
|
20
|
+
FileUtils.mkdir_p(".git/hooks/shared")
|
21
|
+
FileUtils.mkdir_p(".git/hooks/local")
|
22
|
+
|
23
|
+
CLIENT_HOOKS.each do |hook|
|
24
|
+
FileUtils.cp((ASSETS_PATH + "helpers/hookd_wrapper").to_s, ".git/hooks/#{hook}")
|
25
|
+
FileUtils.chmod(0755, ".git/hooks/#{hook}")
|
26
|
+
end
|
27
|
+
|
28
|
+
hooks_rev = remote_hooks_rev
|
29
|
+
|
30
|
+
with_env_var("GIT_OBJECT_DIRECTORY", File.join(Dir.pwd, ".git/objects")) do
|
31
|
+
Dir.chdir(".git/hooks/shared") do
|
32
|
+
unless File.exist?(".git")
|
33
|
+
cmd(*%w[git init])
|
34
|
+
cmd(*%w[git symbolic-ref HEAD refs/heads/--hooks--])
|
35
|
+
cmd(*%w[git commit --allow-empty -m initial\ commit])
|
36
|
+
end
|
37
|
+
cmd(*%w[git reset --hard], hooks_rev) if hooks_rev
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def option_parser
|
43
|
+
super.tap do |opts|
|
44
|
+
opts.banner = "Usage: git hook init"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
def remote_hooks_rev
|
50
|
+
%x{git for-each-ref}.chomp.split("\n").map {|l| l.split(/\s+/) }.each do |rev, kind, ref|
|
51
|
+
return rev if ref == "refs/remotes/origin/--hooks--"
|
52
|
+
end
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
class Gitty::Hook::List < Gitty::Runner
|
3
|
+
include ::Gitty::Helpers
|
4
|
+
include FileUtils
|
5
|
+
KINDS = [:local, :shared, :available]
|
6
|
+
|
7
|
+
def run
|
8
|
+
stdout.puts "Listing hooks"
|
9
|
+
which_to_show = KINDS.select { |k| options[k] }
|
10
|
+
which_to_show = KINDS if which_to_show.empty? # default to everything
|
11
|
+
which_to_show.each { |w| show_hooks(w) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def show_hooks(which)
|
15
|
+
case which
|
16
|
+
when :local then show_local_or_shared_hooks('local')
|
17
|
+
when :shared then show_local_or_shared_hooks('shared')
|
18
|
+
when :available then show_available_hooks
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def show_local_or_shared_hooks(which)
|
23
|
+
hook_names = filenames_in(installed_hooks_path(which))
|
24
|
+
return if hook_names.empty?
|
25
|
+
puts "#{which}:\n#{listify(hook_names)}\n\n"
|
26
|
+
end
|
27
|
+
|
28
|
+
def listify(hooks)
|
29
|
+
hooks.map { |h| "- #{h}" }.join("\n")
|
30
|
+
end
|
31
|
+
|
32
|
+
def filenames_in(dir)
|
33
|
+
if File.directory?(dir)
|
34
|
+
Dir.glob((dir + "*").to_s).sort.map do |path|
|
35
|
+
File.basename(path)
|
36
|
+
end
|
37
|
+
else
|
38
|
+
[]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def installed_hooks_path(which)
|
43
|
+
Pathname.new(".git/hooks/#{which}/hooks")
|
44
|
+
end
|
45
|
+
|
46
|
+
def show_available_hooks
|
47
|
+
all_hooks = Gitty.asset_paths.map {|asset_path| filenames_in(asset_path + "hooks")}.flatten
|
48
|
+
installed_hooks = [:local, :shared].map { |which| filenames_in(installed_hooks_path(which)) }.flatten
|
49
|
+
available_hooks = all_hooks.sort - installed_hooks.sort
|
50
|
+
puts "available:\n#{listify(available_hooks)}\n\n"
|
51
|
+
end
|
52
|
+
|
53
|
+
def option_parser
|
54
|
+
super.tap do |opts|
|
55
|
+
opts.banner = "Usage: git hook list"
|
56
|
+
opts.on("-l", "--local", "Show local hooks") do
|
57
|
+
options[:local] = true
|
58
|
+
end
|
59
|
+
opts.on("-r", "--shared", "Show shared hooks") do
|
60
|
+
options[:shared] = true
|
61
|
+
end
|
62
|
+
opts.on("-a", "--available", "Show available hooks") do
|
63
|
+
options[:available] = true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|