Fun with the upcoming 1.7 release of Git: rebase --interactive --autosquash
The upcoming Git 1.7 has a lot of really nice improvements, and new
features. One of the big new features is the --autosquash argument
for git rebase --interactive.
If you’re anything like me, then you commit a lot, while you’re
working on something, and use git rebase --interactive judiciously to
clean up all these incremental commits into a presentable format. If
you’re a bit more like me, then you’ll often end up doing multiple
git rebase --interactive passes to split commits apart, and squash them
back into other commits.
Git just gained the ability to make this a little faster. If you know
what commit you want to squash something in to you can commit it with a
message of “squash! $other_commit_subject”. Then if you run
git rebase --interactive --autosquash commitish, the line will automatically
be set as squash, and placed below the commit with the subject of
$other_commit_subject.
For example:
1 2 3 4 5 6 7 8 9 10 11 12 |
$ vim Foo.txt $ git commit -am "Change all the 'Bar's to 'Foo's" [topic 8374d8e] Change all the 'Bar's to 'Foo's 1 files changed, 2 insertions(+), 2 deletions(-) $ vim Bar.txt $ git commit -am "Change all the 'Foo's to 'Bar's" [topic 2d12ce8] Change all the 'Foo's to 'Bar's 1 files changed, 1 insertions(+), 1 deletions(-) $ vim Foo.txt $ git commit -am "squash! Change all the 'Bar's" [topic 259a7e6] squash! Change all the 'Bar's 1 files changed, 2 insertions(+), 1 deletions(-) |
If we run git rebase --interactive --autosquash origin/master from
here, the pick-list will look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
pick 8374d8e Change all the 'Bar's to 'Foo's squash 259a7e6 squash! Change all the 'Bar's pick 2d12ce8 Change all the 'Foo's to 'Bar's # Rebase b6bee12..259a7e6 onto b6bee12 # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. # |
When you get to the squash, you’ll have a commit message like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# This is a combination of 2 commits. # The first commit's message is: Change all the 'Bar's to 'Foo's # This is the 2nd commit message: squash! Change all the 'Bar's # Please enter the commit message for your ch anges. Lines starting # with '#' will be ignored, and an empty mess age aborts the commit. # Not currently on any branch. # Changes to be committed: # modified: Foo.txt # |
If you were paying attention earlier to the pick-list, you’ll notice that
there’s also a fixup command available. If we had specified fixup!,
instead of squash! as the commit message’s prefix, then the pick list would
have ended up as:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
pick 8374d8e Change all the 'Bar's to 'Foo's fixup cfc6e54 fixup! Change all the 'Bar's pick 2d12ce8 Change all the 'Foo's to 'Bar's # Rebase b6bee12..cfc6e54 onto b6bee12 # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. # |
With the following in your editor for the combined commit message:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# This is a combination of 2 commits. # The first commit's message is: Change all the 'Bar's to 'Foo's # The 2nd commit message will be skipped: # fixup! Change all the 'Bar's # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # Not currently on any branch. # Changes to be committed: # modified: README.markdown # |
fixup! commit’s message is already commented out. You can just save out the message as-is, and your original commit message will be kept. Very handy for including changes when you realize that you forgot to add part of an earlier commit.
Here’s a few aliases I have setup to make all this easier:
1 2 3 4 |
[alias] fixup = !sh -c 'git commit -m \"fixup! $(git log -1 --format='\\''%s'\\'' $@)\"' - squash = !sh -c 'git commit -m \"squash! $(git log -1 --format='\\''%s'\\'' $@)\"' - ri = rebase --interactive --autosquash |
Here’s how they would be used in our previous example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ vim Foo.txt $ git commit -am "Change all the 'Bar's to 'Foo's" [topic 8374d8e] Change all the 'Bar's to 'Foo's 1 files changed, 2 insertions(+), 2 deletions(-) $ vim Bar.txt $ git commit -am "Change all the 'Foo's to 'Bar's" [topic 2d12ce8] Change all the 'Foo's to 'Bar's 1 files changed, 1 insertions(+), 1 deletions(-) $ vim Foo.txt $ git add Foo.txt $ git squash HEAD~2 [topic 259a7e6] squash! Change all the 'Bar's to 'Foo's 1 files changed, 2 insertions(+), 1 deletions(-) $ git ri origin/master |
Similarly, git fixup HEAD~2 would create a fixup! commit to be used with git rebase --interactive --autosquash (Aliased as: git ri).
Edit 2010-02-13: Fix alias examples.
Capistrano completion in zsh
I’ve decided to try out zsh for a while, and while I already get completions for most everything I want, out of the box, I am missing completions for Capistrano tasks.
I had been using brynary’s Bash Capistrano completion script. I was able to find a mailing list post about setting up Capistrano task completions for zsh, but it didn’t quite work for me. (show_tasks isn’t a valid task.) I also didn’t like throwing the .cap_tasks file in the top-level of the project. I already had a ~/.zsh_cache/ directory for caching zsh’s completions, so I decided to modify the script I found to put the cache file there, instead.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
_cap_does_task_list_need_generating () { if [ ! -f cap_tasks ]; then return 0; else accurate=$(stat -f%m ~/.zsh_cache/cap_tasks-$(echo $PWD | sha512sum)) changed=$(stat -f%m config/deploy.rb) return $(expr $accurate '>=' $changed) fi } _cap () { if [ -f config/deploy.rb ]; then if _cap_does_task_list_need_generating; then cap -T | grep '^cap' | cut -d' ' -f2 >! ~/.zsh_cache/cap_tasks-$(echo $PWD | sha512sum) fi compadd `cat ~/.zsh_cache/cap_tasks-$(echo $PWD | sha512sum)` fi } compdef _cap cap |
~/.zsh.d/S50_capistrano, which automatically gets loaded on startup.
1 2 3 |
for zshrc_snipplet in ~/.zsh.d/S[0-9][0-9]*[^~] ; do source $zshrc_snipplet done |
Fixing the Oniguruma Gem for use on DreamHost
While looking at how to get syntax highlighted source back up on here after switching to Mephisto, I kept running across references to the Ultraviolet gem. Some of the dependencies are a little old,(Oniguruma Gem, Library), but the output looks very nice, from the examples I’d seen.
The problem comes in, that the Oniguruma gem won’t install without you already having the Oniguruma library installed (in a standard system location). This is a pretty well documented problem, with a simple fix.
Unfortunately, I wasn’t even able to get the gem to build at all with
the original Rakefile that comes with it, and gave up very quickly on
trying to fix it. Fortunately, there is a wonderful gem out there
called Jeweler. This allowed me
to trivially setup a working build environment, drop in the original gem
code, and get something up and running.
After adding dir_config to the extconf.rb, you can happily install the
Oniguruma gem (provided your LD_LIBRARY_PATH includes wherever you
installed the library). This gets to be a problem, when using Passenger
on a shared host (such as DreamHost), like I’m trying to do.
Fortunately, there’s a way to fix this (at least on Linux). When
linking in the libraries, you can tell ld to include path information
on where to look for them. This is very handy.
Here’s the extconf.rb that I ended up going with:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
require 'mkmf' onig_dirs = dir_config('onig') onig_libs = onig_dirs.pop ldshared = CONFIG['LDSHARED'] if !onig_libs.nil? onig_libs.split(File::PATH_SEPARATOR).each do |p| ldshared += " -Wl,-rpath,#{p}" end end CONFIG['LDSHARED'] = ldshared have_library("onig") $CFLAGS='-Wall' create_makefile( "oregexp" ) |
With this, I was able to build the gem, install it locally, and still have it work with passenger. I can’t guarantee it’s the best way to do it, but it works.
I’ve put the modified Oniguruma gem on GitHub
Until GitHub gets the gem building back up and running, you’ll have to
download, and make the .gem file yourself, unfortunately.
1 2 3 4 |
git clone git://github.com/jhelwig/oniguruma cd oniguruma rake build gem install pkg/oniguruma-$(rake version | sed -e '/oniguruma/d' -e 's/Current version: //').gem -- --with-onig-dir $HOME |
Unhappy with the standard Rails Authorization plugins
1 2 3 4 5 6 |
# Assign user the "global" role 'administrator' user.has_role 'administrator' # Assign user the role "moderator" for the class Group user.has_role 'moderator', Group # Assigns user the role "member" for the instance (of class Group) user.has_role 'member', club |
1 2 3 4 5 6 7 8 9 10 11 |
user.has_role? 'administrator' # => true user.has_role? 'administrator', Group # => false user.has_role? 'administrator', club # => false user.has_role? 'moderator' # => true user.has_role? 'moderator', Group # => true user.has_role? 'moderator', club # => false user.has_role? 'member' # => true user.has_role? 'member', Group # => true user.has_role? 'member', club # => true |
1 2 3 4 5 6 7 8 9 10 11 |
user.has_role? 'administrator' # => true user.has_role? 'administrator', Group # => true user.has_role? 'administrator', club # => true user.has_role? 'moderator' # => false user.has_role? 'moderator', Group # => true user.has_role? 'moderator', club # => true user.has_role? 'member' # => false user.has_role? 'member', Group # => false user.has_role? 'member', club # => true |
1 2 |
user.has_role :administrator user.has_role? :administrator, Group |
1 2 3 4 5 6 |
user.has_role :administrator => nil, :moderator => Group, :member => club user.has_role? :administrator => Group, :moderator => club # => true user.has_role? :administrator => nil, :moderator => nil # => false |
nil, is the same as saying that the scope is global. Secondly, when doing has_role? with a hash, all role requirements must be met for has_role? to be true.
Git + Lighthouse
1 |
git clone http://technosorcery.net/git/lighthouseapp-git-hook/ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
#!/usr/bin/perl -w use strict; binmode STDIN, ':utf8'; use LWP::UserAgent (); use Date::Format qw/ time2str /; use HTTP::Request::Common qw/ POST /; use XML::Simple qw/ XMLout /; use YAML qw/ Dump /; ################################################# # Configuration Options # ################################################# my $git = '/home/jhelwig/bin/git'; my %tokens = ( # Fallback token to use if one isn't found for an author. 'default' => '', # Individual tokens for author/committer. # If author email can't be found, committer's email will be tried. 'jacob@technosorcery.net' => undef, 'Another Author' => undef, ); my $lighthouseapp_account_url = 'http://account-name.lighthouseapp.com'; my $lighthouseapp_project_id = 0; ################################################# shift @ARGV; my $old_sha1 = shift @ARGV; my $new_sha1 = shift @ARGV; my $rev_list = `$git rev-list --pretty=format:"" $old_sha1..$new_sha1`; $rev_list =~ s/^commit //gm; my @revs = reverse(split("\n", $rev_list)); foreach my $rev (@revs) { chomp(my $author_name = `$git show --pretty=format:"%an" $rev | sed q`); chomp(my $author_email = `$git show --pretty=format:"%ae" $rev | sed q`); chomp(my $author_date = `$git show --pretty=format:"%aD" $rev | sed q`); chomp(my $committer_name = `$git show --pretty=format:"%cn" $rev | sed q`); chomp(my $committer_email = `$git show --pretty=format:"%ce" $rev | sed q`); chomp(my $committer_date = `$git show --pretty=format:"%cD" $rev | sed q`); chomp(my $changed_at = time2str("%Y-%m-%dT%TZ", `$git show --pretty=format:"%ct" $rev | sed q`, 'GMT')); chomp(my $commit_log = `$git log -n1 --pretty=medium $rev | sed '1,4d'`); my $body = <<"HERE"; $commit_log Author: $author_name <$author_email> AuthorDate: $author_date Commit: $committer_name <$committer_email> CommitDate: $committer_date HERE chomp(my $commit_subject = `$git show --pretty=format:"%s" $rev | sed q`); my $changes_yaml = Dump([ map { [ split(/\s+/, $_) ] } split("\n", `$git diff-tree -r --name-status $rev | sed '1d'`) ]); my $xml = XMLout({ 'changeset' => { 'title' => [ $commit_subject, ], 'body' => [ $body, ], 'revision' => [ $rev, ], 'changes' => { 'type' => 'yaml', 'content' => $changes_yaml, }, 'changed-at' => { 'type' => 'datetime', 'content' => $changed_at, }, }}, KeepRoot => 1, ); my $lh_token = defined($tokens{$author_email}) ? $tokens{$author_email} : defined($tokens{$committer_email}) ? $tokens{$committer_email} : $tokens{'default'}; my $ua = LWP::UserAgent->new(); $ua->timeout(3); $ua->env_proxy(); my $response = $ua->simple_request(POST( "$lighthouseapp_account_url/projects/$lighthouseapp_project_id/changesets.xml", 'content-type' => 'application/xml', 'X-LighthouseToken' => $lh_token, Content => $xml )); unless ($response->is_success()) { print $response->status_line() . "\n" . $response->content() . "\n\n"; } } |
Soft resolution of Request Tracker tickets.
etc/RT_SiteConfig.pm:
1 |
@InactiveStatus = qw(resolved rejected pending deleted) unless @InactiveStatus; |
local/lib/RT/Action/AutoResolve.pm ((I'd love to give credit on where I found the code that this is based on, but I can't seem to find it on the RT Wiki anymore. If you can find the original, let me know so I can give proper credit for this.)) with the following content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package RT::Action::AutoResolve; require RT::Action::Generic; use strict; use vars qw/@ISA/; @ISA=qw(RT::Action::Generic); sub Describe { my $self = shift; return (ref $self ); } sub Prepare { my $self = shift; # if the ticket is already resolved don't re-resolve it. if ( ( $self->TicketObj->Status eq 'resolved' ) ) { return undef; } else { return (1); } } sub Commit { my $self = shift; $self->TicketObj->SetStatus( 'resolved' ); return (1); } 1; |
--action argument to rt-crontool.
Create local/lib/RT/Condition/UntouchedInDays.pm ((Modified from UntouchedInHours.)) for the --condition used with rt-crontool:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package RT::Condition::UntouchedInDays; require RT::Condition::Generic; use RT::Date; @ISA = qw(RT::Condition::Generic); use strict; use vars qw/@ISA/; sub IsApplicable { my $self = shift; if ((time()-$self->TicketObj->LastUpdatedObj->Unix)/3600/24 >= $self->Argument) { return 1 } else { return 0; } } 1; |
local/lib/RT/Search/PendingTicketsInQueue.pm will be used for the --search. ((I can't seem to find where I got this code from originally. If you recognize it, or are the original author, please let me know.))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package RT::Search::PendingTicketsInQueue; use strict; use base qw(RT::Search::Generic); sub Describe { my $self = shift; return ($self->loc("No description for [_1]", ref $self)); } sub Prepare { my $self = shift; $self->TicketsObj->LimitQueue(VALUE => $self->Argument); $self->TicketsObj->LimitStatus(VALUE => 'pending'); return(1); } 1; |
rt-pending script that should be called daily to resolve tickets that have been set as pending for n days (in this example: n = 7). ((I just dropped this script into /etc/cron.daily/, but I'm using Debian. You should adjust as appropriate for your distribution.))
1 2 3 |
#! /bin/sh /opt/rt3/bin/rt-crontool --search RT::Search::PendingTicketsInQueue --search-arg 'Help Desk' --condition RT::Condition::UntouchedInDays --condition-arg 7 --action RT::Action::AutoResolve |
local/lib/RT/Condition/ReplyToPending.pm ((Heavily based on ReplyToResolved.pm))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package RT::Condition::ReplyToPending; use strict; use base qw(RT::Condition::Generic); sub IsApplicable { my $self = shift; my $ticket = $self->TicketObj; my $transaction = $self->TransactionObj; if ( $transaction->Type eq 'Correspond' && $ticket->Status eq 'pending' && $transaction->Creator != 1 ) { # prevent loop return(1); } else { return(undef); } } 1; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#!/usr/bin/perl use strict; use Unicode::String qw(utf8 latin1); # Replace this with your RT_LIB_PATH use lib "/opt/rt3/lib"; # Replace this with your RT_ETC_PATH use lib "/opt/rt3/etc"; use RT; use RT::Interface::CLI qw( CleanEnv GetCurrentUser ); use RT::ScripCondition; CleanEnv(); RT::LoadConfig(); RT::Init(); ##Drop setgid permissions RT::DropSetGIDPermissions(); ##Get the current user all loaded our $CurrentUser = GetCurrentUser(); unless( $CurrentUser->Id ) { print "No RT user found. Please consult your RT administrator.\n"; exit 1; } my $sc = new RT::ScripCondition($CurrentUser); $sc->Create( Name => 'On Reply to Pending', Description => "Reply to a ticket marked as pending.", ExecModule => 'ReplyToPendingTicket', ApplicableTransTypes => 'Any' ); |
On Reply to Pending, and the action set to Open Ticket. This will re-open any tickets as soon as someone replies to them, but not if anyone comments on them. (Commenting will, however delay the resolution of a ticket.)
You'll then need to create either a global scrip, or a queue specific scrip for each queue where you want people to be able to reply to a pending ticket, and have it re-opened. (Personally I recommend the global scrip method.)
This step requires direct DB access. You'll need to be able to do an insert on the ScripConditions table. <user_id> should be replaced with a valid user ID, taken from one of the other entries in the ScripConditions table.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
INSERT INTO ScripConditions( Name, Description, ExecModule, Argument, ApplicableTransTypes, Creator, Created, LastUpdatedBy, LastUpdated ) VALUES ( 'On Pending', 'Whenever a ticket is marked as pending resolution', 'StatusChange', 'pending', 'Status', <user_id>, NOW(), <user_id>, NOW() ); |
Welcome!
GPG
- Key ID: 0x4612D29E282E4321
- Key Fingerprint: 8445 B661 E215 BD04 A363 A52E 4612 D29E 282E 4321