gitty 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Tim Harper
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,21 @@
1
+ h1. Gitty : Unobtrusively extend git.
2
+
3
+ Gitty provides an interface to manage commonly used git-hooks for repositories. A few use cases are:
4
+
5
+ * Make it so that git submodules are intellegently updated after switching branches or merging.
6
+ * Prevent trailing whitespace from being committed.
7
+ * Help prevent scratch / debug code from being committed by refusing to commit a KEYWORD that marks it as such.
8
+ * Share hooks with collaborators
9
+
10
+ h2. Currently in alpha!
11
+
12
+ Missing features:
13
+
14
+ * List available hooks
15
+ * List installed hooks
16
+ * Only receive hooks from trusted publishers. (currently when gitty is activated on a repository, any hook published to origin is automatically installed)
17
+ * It's very young and might break
18
+
19
+ h2. Issues
20
+
21
+ Report them here: http://github.com/timcharper/gitty/issues
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "gitty"
8
+ gem.summary = %Q{Unobtrusively extend git}
9
+ gem.description = %Q{Unobtrusively extend git}
10
+ gem.email = "timcharper@gmail.com"
11
+ gem.homepage = "http://github.com/timcharper/gitty"
12
+ gem.authors = ["Tim Harper"]
13
+ gem.files = Dir['lib/**/*.rb'] + Dir['assets/**/*'] + Dir['bin/*'] + Dir['cucumber/**/*'] + Dir['spec/**/*'] + %w[cucumber.yml Rakefile README.textile LICENSE]
14
+ # gem.executables << 'git-hook'/
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'rake/rdoctask'
22
+ Rake::RDocTask.new do |rdoc|
23
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
24
+
25
+ rdoc.rdoc_dir = 'rdoc'
26
+ rdoc.title = "gitty #{version}"
27
+ rdoc.rdoc_files.include('README*')
28
+ rdoc.rdoc_files.include('lib/**/*.rb')
29
+ end
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env ruby
2
+ require "tempfile.rb"
3
+ require 'optparse'
4
+
5
+ DEFAULT_STABLE_BRANCH="master"
6
+ EDITOR=ENV['EDITOR'] || 'vim'
7
+
8
+
9
+ class GitCleanBranchesRunner
10
+ module CandidateState
11
+ module ConsiderAll
12
+ def candidate_instructions
13
+ "# The following is a list of all local and remote branches in your repository"
14
+ end
15
+
16
+ def branch_candidates
17
+ @branch_candidates ||= branches
18
+ end
19
+ end
20
+
21
+ module MergedOnly
22
+ def branch_candidates
23
+ @branch_candidates ||= (
24
+ candidates = []
25
+ branches.each do |branch|
26
+ next if %r(/(#{preserve_always * '|'})$) =~ branch
27
+ next unless `git rev-list origin/#{stable_branch}..#{branch}`.strip.empty?
28
+ candidates << branch
29
+ end
30
+ candidates
31
+ )
32
+ end
33
+
34
+ def candidate_instructions
35
+ "# The following branches have been merged in to refs/remotes/origin/#{stable_branch}"
36
+ end
37
+ end
38
+ end
39
+
40
+ attr_reader :stable_branch
41
+
42
+ def initialize(args, output = STDOUT, error = STDERR)
43
+ @output, @error = output, error
44
+ extend CandidateState::MergedOnly
45
+ @stable_branch = DEFAULT_STABLE_BRANCH
46
+ opts = OptionParser.new
47
+ opts.banner = "Usage: #{$0} [options]"
48
+ opts.separator "Options:"
49
+ opts.on("-a", "--all", "Consider all branches for deletion, regardless if they've been merged into stable") do
50
+ extend CandidateState::ConsiderAll
51
+ end
52
+ opts.on("-b", "--branch [branch]", "The stable branch (default: #{@stable_branch}). If a branch is merged into this, and -a parameter not specified, consider it for deletion.") do |b|
53
+ @stable_branch = b
54
+ end
55
+ opts.parse!(args)
56
+ end
57
+
58
+ def preserve_always
59
+ [stable_branch] + %w[staging master next HEAD]
60
+ end
61
+
62
+ def fetch_and_prune
63
+ puts "Fetching and pruning all remotes"
64
+ `git remote`.each do |remote|
65
+ system("git fetch #{remote}")
66
+ system("git remote prune #{remote}")
67
+ end
68
+ end
69
+
70
+ def branches
71
+ %x(git for-each-ref).map do |branch_name|
72
+ next unless %r{.+(refs/(heads|remotes).+$)}.match(branch_name)
73
+ $1
74
+ end.compact
75
+ end
76
+
77
+ def candidate_instructions
78
+ raise NotImplemented
79
+ end
80
+
81
+ def branch_candidates
82
+ raise NotImplemented
83
+ end
84
+
85
+ def edit_branch_list
86
+ file = Tempfile.new("cleanup-branch")
87
+ file.puts candidate_instructions
88
+ file.puts "# To delete the branches, delete them from this list, and then save and quit"
89
+ file.puts(branch_candidates * "\n")
90
+ file.close
91
+ system("#{EDITOR} #{file.path}")
92
+ preserve_branches = File.read(file.path).split("\n").grep(/^[^#].+/)
93
+ file.delete
94
+
95
+ unless (erroneous_branches = (preserve_branches - branch_candidates)).empty?
96
+ puts <<EOF
97
+ Error! unrecognized branches:
98
+ #{erroneous_branches.map{|b| " - #{b}"} * "\n"}
99
+ EOF
100
+ exit 1
101
+ end
102
+
103
+ preserve_branches
104
+ end
105
+
106
+ def sort_branches(branches)
107
+ sorted_branches = {}
108
+ branches.each do |branch|
109
+ case branch
110
+ when %r(^refs/remotes/([^/]+)/(.+)$)
111
+ remote, branch = $1, $2
112
+ sorted_branches[remote] ||= []
113
+ sorted_branches[remote] << branch
114
+ when %r(^refs/heads/(.+)$)
115
+ sorted_branches[nil] ||= []
116
+ sorted_branches[nil] << $1
117
+ else
118
+ puts "I don't know how to delete #{branch_for_deletion}"
119
+ end
120
+ end
121
+ sorted_branches
122
+ end
123
+
124
+ def delete_branches(branches_for_deletion)
125
+ sorted_branches = sort_branches(branches_for_deletion)
126
+ if sorted_branches.empty?
127
+ puts "No branches to delete."
128
+ exit 1
129
+ end
130
+ puts <<EOF
131
+ Deleting branches:
132
+ #{branches_for_deletion.map{|b| " - #{b}"} * "\n"}
133
+ EOF
134
+ sorted_branches.each do |remote, branches|
135
+ if remote.nil?
136
+ system(*%w[git branch -D] + branches)
137
+ else
138
+ system(*%w[git push] + [remote] + branches.map { |b| ":#{b}" })
139
+ end
140
+ end
141
+ end
142
+
143
+ def run
144
+ fetch_and_prune
145
+ preserve_branches = edit_branch_list
146
+ delete_branches(branch_candidates - preserve_branches)
147
+ end
148
+ end
149
+
150
+
151
+ GitCleanBranchesRunner.new(ARGV).run
@@ -0,0 +1,30 @@
1
+ #!/bin/bash
2
+
3
+ # strip trailing whitespace from any lines you've introduced. If you've staged changes, it strips them from the index (and they stay in your working copy).
4
+ # why would you want to do this? Keep your patches clean. Why not just strip all whitespaces from your project? Well... then you'd have a big nasty commit, wouldn't you? And some code generators might rely on the whitespace (like annotate-models).
5
+
6
+ STAGED_CHANGES="$(git diff --cached)"
7
+ if [ "$STAGED_CHANGES" != "" ]; then
8
+ echo "Stripping trailing spaces from index"
9
+ git reset 2> /dev/null 1> /dev/null
10
+ if (echo "$STAGED_CHANGES" | git apply --whitespace=fix --cached); then
11
+ echo "Successfully stripped all new whitespaces from the index"
12
+ else
13
+ BLEW_UP_FILE="/tmp/git-strip-new-whitespace-blew-up"
14
+ echo "$STAGED_CHANGES" > "$BLEW_UP_FILE"
15
+ echo "Something went wrong. I wrote the patch for your staged changes to $BLEW_UP_FILE."
16
+ echo "Apply them back to your index with 'cat $BLEW_UP_FILE | git apply --cached'"
17
+ exit 1
18
+ fi
19
+ exit 0
20
+ fi
21
+
22
+ git stash
23
+ if (git stash show -p | git apply --whitespace=fix); then
24
+ echo "Successfully stripped all new whitespaces"
25
+ git reset 2> /dev/null 1> /dev/null
26
+ git stash drop 2> /dev/null 1> /dev/null
27
+ else
28
+ echo "Something went wrong. Your changes were stashed at least :S"
29
+ fi
30
+ exit 0
@@ -0,0 +1,262 @@
1
+ #!/bin/bash
2
+
3
+ if [ "`which md5`" == "" ]; then
4
+ if [ "`which md5sum`" == "" ]; then
5
+ echo "Couldn't find md5 or md5sum. Aborting"
6
+ exit 1
7
+ fi
8
+
9
+ md5 () {
10
+ md5sum | cut -f 1 -d " "
11
+ }
12
+ fi
13
+
14
+ list_submodules () {
15
+ git ls-files --stage | grep "^160000 " | cut -f 2
16
+ }
17
+
18
+ submodule_url () {
19
+ git config "submodule.$1.url"
20
+ }
21
+
22
+ submodule_md5 () {
23
+ local path="$1"
24
+ local url=`submodule_url "$path"`
25
+ local string="$path $url"
26
+ printf "$string" | md5
27
+ }
28
+
29
+ submodule_stash_path () {
30
+ local path="$1"
31
+ [ ! -d ".git/submodule_cache/" ] && mkdir .git/submodule_cache
32
+ printf ".git/submodule_cache/`submodule_md5 "$path"`"
33
+ }
34
+
35
+ submodule_helper_stash () {
36
+ list_submodules | while read submodule; do
37
+ [ ! -d "$submodule/.git" ] && continue
38
+ stash_path="`submodule_stash_path "$submodule"`"
39
+ if [ -d "$stash_path" ]; then
40
+ echo "'$stash_path' already exists: it appears that the submodule $submodule has already been stashed"
41
+ else
42
+ mv "$submodule" "$stash_path"
43
+ echo "Stashed '$submodule' to '$stash_path'"
44
+ fi
45
+ done
46
+ }
47
+
48
+ submodule_helper_unstash () {
49
+ list_submodules | while read submodule; do
50
+ [ -d "$submodule/.git" ] && continue
51
+ stash_path="`submodule_stash_path "$submodule"`"
52
+ if [ ! -d "$stash_path" ]; then
53
+ echo "'$stash_path' doesn't exist, can't restore from stash (maybe it was never stashed?)"
54
+ else
55
+ if [ -d "$submodule" ] && ! rmdir "$submodule"; then
56
+ echo "Path '$submodule' is in the way. Leaving stashed."
57
+ continue
58
+ fi
59
+ mv "$stash_path/" "$submodule/"
60
+ echo "Unstashed '$submodule' from '$stash_path'"
61
+ fi
62
+ done
63
+ }
64
+
65
+ submodule_helper_stash_status () {
66
+ list_submodules | while read submodule; do
67
+ stash_path="`submodule_stash_path "$submodule"`"
68
+ if [ -d "$submodule/.git" ]; then
69
+ status="checked out"
70
+ elif [ -d "$stash_path" ]; then
71
+ status="stashed"
72
+ else
73
+ status="????"
74
+ fi
75
+
76
+ if [ "$status" == "stashed" ]; then
77
+ display_stash_path="$stash_path"
78
+ else
79
+ display_stash_path=""
80
+ fi
81
+ printf "(%s)\t%s\t%s\n" "$status" "$display_stash_path" "$submodule"
82
+ done
83
+ }
84
+
85
+ submodule_helper_attach () {
86
+ if (cat .git/HEAD | grep ref: 1> /dev/null) ; then
87
+ echo "You're already on a branch"
88
+ exit 1
89
+ fi
90
+ current_rev=$(git rev-parse HEAD)
91
+ line=$(git 'for-each-ref' | egrep -v 'HEAD$' | grep $current_rev)
92
+ if [ $? != 0 ]; then
93
+ echo "no known branch is currently at ${current_rev}"
94
+ echo "here are some suggestions: "
95
+ git branch --contains $current_rev -a | egrep -v 'no branch|HEAD'
96
+ exit 1
97
+ fi
98
+ rev=$(echo $line | awk '{print $1}')
99
+ ref=$(echo $line | awk '{print $3}')
100
+ if ( echo $ref | egrep '^refs/heads/' > /dev/null ); then
101
+ echo "ref: $ref" > .git/HEAD
102
+ exit 0
103
+ fi
104
+
105
+ if ( echo $ref | egrep '^refs/remotes/' > /dev/null ); then
106
+ echo "Remote branch ${ref} matches"
107
+ short_ref=${ref#refs/remotes/}
108
+ local_ref=${ref#*/*/*/}
109
+ divergent_commits=$(git rev-list $ref..$local_ref)
110
+ if [ "$divergent_commits" == "" ]; then
111
+ echo "fastforwarding $local_ref to $ref"
112
+ git branch -f "$local_ref" "$ref"
113
+ git checkout "$local_ref"
114
+ fi
115
+ exit 1
116
+ fi
117
+ }
118
+
119
+ submodule_helper_status () {
120
+ local prefix="$1"
121
+ local status=$(git status)
122
+ if [ "$(printf "$status" | grep "nothing to commit (working directory clean)")" == "" ]; then
123
+ echo
124
+ echo "${prefix}"
125
+ echo ----------------------------------------
126
+ echo "$status"
127
+ fi
128
+ list_submodules | while read submodule; do
129
+ pushd $submodule > /dev/null
130
+ submodule_helper_status "${prefix}${submodule}"
131
+ popd > /dev/null
132
+ done
133
+ }
134
+
135
+ submodule_helper_add_folder () {
136
+ local folder="$1"
137
+ pushd $folder > /dev/null
138
+ url=$(git config remote.origin.url)
139
+ popd > /dev/null
140
+ git submodule add "$url" "$folder"
141
+ }
142
+
143
+ submodule_helper_fetch () {
144
+ local prefix="$1"
145
+ local status=$(git status)
146
+ echo
147
+ echo "${prefix}"
148
+ echo ----------------------------------------
149
+ git fetch
150
+
151
+ list_submodules | while read submodule; do
152
+ pushd $submodule > /dev/null
153
+ submodule_helper_fetch "${prefix}${submodule}"
154
+ popd > /dev/null
155
+ done
156
+ }
157
+
158
+ submodule_helper_update () {
159
+ git ls-files --stage | grep "^160000 " | while read line; do
160
+ submodule=$(printf "$line" | cut -f 2 )
161
+ version=$(printf "$line" | cut -f 1 | cut -f 2 -d ' ' )
162
+ pushd $submodule > /dev/null
163
+ current_version=$(git rev-parse HEAD)
164
+ if [ ! "$current_version" == "$version" ]; then
165
+ echo
166
+ echo "${submodule}"
167
+ echo ----------------------------------------
168
+ if [ "$1" != "--offline" ]; then
169
+ (git show "$version" 2> /dev/null 1> /dev/null) || git fetch
170
+ fi
171
+ git checkout $version
172
+ fi
173
+ popd > /dev/null
174
+ done
175
+ }
176
+
177
+ submodule_helper_checkout () {
178
+ echo 1
179
+ }
180
+
181
+ submodule_helper_help () {
182
+ cat <<EOF
183
+ $0
184
+ V 0.2
185
+ By Tim Harper
186
+ http://tim.theenchanter.com/
187
+
188
+ A script to help you manage submodules
189
+
190
+ commands:
191
+
192
+ stash - Takes all of your submodules, and stashes them temporarily
193
+ to .git/submodule_cache/ Useful if you want to switch to a
194
+ branch that with a file that gets in the way of of your
195
+ submodule
196
+
197
+ unstash - Takes any stashed modules that are referenced by the
198
+ current branch, and puts them back in their place
199
+
200
+ stash-status
201
+ - Show the stashed status of any submodules (and the path
202
+ where they were stashed at)
203
+
204
+ fetch - Fetch all submodules (good for if you plan on going offline and
205
+ want to make sure you have everything
206
+
207
+ update - Optimized submodule update - only tries to fetch when needed. Use
208
+ --offline to prevent it from trying to fetch when encountering a
209
+ non-existing revision.
210
+
211
+ status - See the status for all submodules
212
+
213
+ attach - Re-attach your HEAD to a branch that matches your current rev.
214
+
215
+ add-folder
216
+ - add a folder that has been cloned as a submodule already
217
+
218
+
219
+ DISCLAIMER: Make sure you back things up first in case things go horribly wrong
220
+ EOF
221
+ }
222
+
223
+ if [ ! -d ".git" ]; then
224
+ echo "This must be run from the root directory of the git working copy"
225
+ exit 1
226
+ fi
227
+
228
+ case "$1" in
229
+ stash)
230
+ submodule_helper_stash
231
+ ;;
232
+ unstash)
233
+ submodule_helper_unstash
234
+ ;;
235
+ stash-status)
236
+ submodule_helper_stash_status
237
+ ;;
238
+ add-folder)
239
+ submodule_helper_add_folder $2
240
+ ;;
241
+ update)
242
+ submodule_helper_update $2
243
+ ;;
244
+ fetch)
245
+ submodule_helper_fetch /
246
+ ;;
247
+ checkout)
248
+ submodule_helper_checkout
249
+ ;;
250
+ status)
251
+ submodule_helper_status /
252
+ ;;
253
+ attach)
254
+ submodule_helper_attach
255
+ ;;
256
+ help)
257
+ submodule_helper_help
258
+ ;;
259
+ *)
260
+ submodule_helper_help
261
+ ;;
262
+ esac