gitty 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/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
|