Tips and tricks

From Koha Wiki
Jump to navigation Jump to search

git tips and tricks

This page collect all git tips and tricks Koha developers have found


git

Hooks

A nice feature of git is hooks.

A hook is a small (or large) script that is executed before or after running some git command.

Hooks are located in the .git/hooks directory; To enable one simply add a file named after the hook point you wish to use to that directory and ensure it is executable.

pre-commit

This pre-commit hook will check for common coding issues before allowing you to complete a commit:

#!/usr/bin/perl

use Modern::Perl;

use File::Basename;
use Term::ANSIColor qw(colored);

$ENV{LOG} = "test";

my $nb_errors = 0;
my @compiled_css = ( "staff-global", "opac" );

for my $filepath (`git diff --cached --name-only`) {
    chomp $filepath;
    next if not -f $filepath;

    my @file_infos = fileparse( $filepath, qr/\.[^.]*/ );

    if ( grep( $file_infos[0], @compiled_css ) && $file_infos[2] eq ".css" ) {
        say colored( "You don't want to commit $filepath", 'red' );
        $nb_errors++;
    }

    if ( $file_infos[2] =~ /^.pl|^.pm$/ ) {
        # If you are not using koha-testing-docker or you use this line instead
        # system(qq{/usr/bin/perl -wc $filepath}) == 0
        #   or say "\n" and $nb_errors++;

        system( qq{ 
            docker exec koha_koha_1 /usr/bin/perl -wc /kohadevbox/koha/$filepath 2>&1 | grep -Pv "Subroutine \\w+ redefined at "
        } ) == 0
          or say "\n" and $nb_errors++;
    } 
    elsif ( $file_infos[2] =~ /^.tt$/ ) {

        #TODO
    }
    elsif ( $file_infos[2] =~ /^.js$/ ) {

        #TODO
    }
    elsif ( $file_infos[2] =~ /^\.vue$/ ) { 
        qx{yarn --silent run prettier --trailing-comma es5 --semi false --arrow-parens avoid --write $filepath};
    }
}

my $filepath;
for my $l ( split '\n', `git diff-index -p -M --cached HEAD` ) {
    if ( $l =~ /^diff --git a\/([^ ]*) .*$/ ) {
        $filepath = $1;
    }
    if ( $l =~ /console.log/ ) {
        say colored( "$filepath contains console.log ($l)", 'red' );
        $nb_errors++;
    }
    elsif ( $l =~ /^\+ *warn Data::Dumper::Dumper / ) {
        say colored( "$filepath contains warn Data::Dumper::Dumper ($l)",
            'red' );
        $nb_errors++;
    }

    # This one could be uncommented when Koha will have the Logger module
    elsif ( $l =~ /^\+ *warn / ) {
        say "$filepath contains warn ($l)";
        $nb_errors++;
    }

    elsif( $l =~ /\+ *\t/) { # fail also if there is some space before the tab
        say colored( "$filepath contains a tab, must use 4 spaces ($l)",
            'red' );
        $nb_errors++;
    }

    elsif ( $l =~ m/^<<<<<<</ or $l =~ m/^>>>>>>>/ or $l =~ m/^=======/ ) {
        say colored( "$filepath contains $& ($l)", 'red' );
    }

}

if ($nb_errors) {
    say "\nAre you sure you want to commit ?";
    say "You can commit with the --no-verify argument";
    exit 1;
}

say colored( "Success, pre-commit checks passed", 'green' );

exit 0;

The above script checks that:

  • All the perl scripts you want to commit are compiling (perl -wc)
  • You are not committing compiled CSS files (CSS generated from SCSS).
  • There are no trailing conflict markers (<<<<, >>>>>, =====)
  • There are no trailing `warn` or `warn Data::Dumper::Dumper` additions
  • There is no file called {something}.log

pre-applypatch

The pre-commit hook can also be useful as a pre-applypatch hook to catch issues with others bugs when applying via either `git am` or `git bz`.

pre-push

This pre-push hook will check for common coding mistakes before allowing the RM or RMaint to push to their community branch:

#!/usr/bin/perl

use Modern::Perl;
use List::MoreUtils qw( any );
use Term::ANSIColor qw(colored);

my $RELEASE   = '19.06.x';                                            # The branch you are managing/maintaining
my $REMOTE    = 'upstream';                                           # The name you gave to the remote for git.koha-community.org
my $SIGNATURE = q|Martin Renvoize <martin.renvoize@ptfs-europe.com>|; # Your signature for sign-off lines

my $remote   = $ARGV[0];
my $minor    = $RELEASE;
my $upstream = ( $RELEASE =~ m{^\d\d\.(05|11)\.x$} ) ? $RELEASE : 'main';
$minor =~ s/x//g;
if ( $remote eq $REMOTE ) {

    say "Running pre-push hook\n";

    # Check branch
    my $current_branch = `git branch | grep \\* | cut -d ' ' -f2`;
    chomp $current_branch;
    if ( $current_branch ne $RELEASE ) {
        say colored( "Current branch is not $RELEASE", 'red' );
        exit 1;
    }

    # Check commits exist
    my @commits = `git rev-list $REMOTE/$upstream..HEAD`;
    unless (@commits) {
        say colored( 'Hum... no commits to push?', 'green' );
        exit 1;
    }

    # Check commit messages are well formed
    my @errors;
    for my $commit (@commits) {

         say colored("Working on $commit", 'green');

        my $commit_message = `git log --format=%s -1 $commit`;
        if ( $commit_message !~ m|^Bug\s\d{4,5}: | ) {
            push @errors,
              colored( "Does not start with 'Bug XXXXX: ' - $commit", 'red' );
        }

        if ( $commit_message =~ m|DO NOT PUSH| ) {
            push @errors,
                colored( "Commit contains DO NOT PUSH - $commit", 'red');
        }

        if ( $commit_message =~ m|DBRev|i
            and not $commit_message =~ m|DBRev $minor| )
        {
            warn $commit_message;
            push @errors,
              colored( "DBRev is wrong, should start with 'DBRev $RELEASE'",
                'red' );
        }

        my $body = `git log --format=%b -1 $commit`;
        $body =~ s|\n\s*|\n|g;
        if ( $body !~ m|\n?Signed-off-by: $SIGNATURE| ) {
            push @errors, colored( "No Signed-off-by line - $commit", 'red' );
        }

        my $author_name = `git log --format=%an -1 $commit`;
        if ( $author_name eq 'John Doe' ) {
            push @errors, colored( "Bad author name - $commit", 'red' );
        }

        my $author_email = `git log --format=%ae -1 $commit`;
        if ( $author_email eq 'you@example.com' ) {
            push @errors, colored( "Bad author email - $commit", 'red' );
        }
    }

    # Check for remaining atomicupdate files
    my @atomicupdate_files =
`git show HEAD:installer/data/mysql/atomicupdate/|tr -s '\\n' | grep -v '^tree'|grep -v 'README'|grep -v skeleton`;
    chomp for @atomicupdate_files;
    for my $atomic (@atomicupdate_files) {
        push @errors, colored( "Atomicupdate file exists - $atomic", 'red' );
    }

    my @dbrev_files = `git show HEAD:installer/data/mysql/db_revs/|tr -s '\\n' | grep -v '^tree'`;
    chomp for @dbrev_files;
    for my $f ( @dbrev_files ) {
        my $mode = (stat("installer/data/mysql/db_revs/$f"))[2] & 07777;
        push @errors, "dbrev file is missing the +x flag - $f" if !($mode == 0775 || $mode == 0755);
    }

    # Check for missing CSS Compiles
    my $compiled = `yarn build && yarn build --view=opac`;
    my @css_changes = `git diff --name-only`;
    chomp for @css_changes;
    for my $css_change (@css_changes) {
        push @errors, colored( "CSS file changes exist - $css_change", 'red' );
    }

    # Report any errors
    if (@errors) {
        say colored($_, 'red') for @errors;
        exit 1;
    }

    say colored("Passed tests, pushing...", 'green');

}

exit 0;

This hook will prevent:

  • to push a local branch that is not named "$RELEASE"
  • to push commits that are not signed off by the RM/RMaint
  • to push commits that do not start with "Bug XXXXX: "
  • to push DBRev commits that do not correctly formatted ("DBRev $RELEASE")
  • to push atomicupdate files

Display the branch you're on

If you want to permanently display the branch you're on, edit your .bashrc file and add the following at the end:

#prompt git
GIT_PS1_SHOWDIRTYSTATE=1
GIT_PS1_SHOWUNTRACKEDFILES=0
GIT_PS1_SHOWSTASHSTATE=1
GIT_PS1_SHOWUPSTREAM="verbose"
PS1='\D{%H:%M} \[\033[1;35m\]\w$(__git_ps1 " \[\033[1;34m\](%s)")\[\033[0m\]\$ '

your display will look like this:

17:47 ~/koha.dev/koha-community (new/bug_7190 $%)$ 

displaying the directory you're on, the branch, and some informations about the status of your working directory


Git aliases to simplify command tasks

If you have Git-BZ installed, you can add the following aliases to your .gitconfig file to make applying a patch and reattaching it with your signoff a 1-command affair:

[alias]
      so = !sh -c 'prove t xt && git commit --amend -s && git bz attach -e $1 HEAD' -
      qa = !sh -c 'git fetch origin main && git checkout -b bug$1-qa origin/main&& git bz apply $1' -
      gr = log --graph --full-history --all --color --pretty=tformat:"%x1b[31m%h%x09%x1b[32m%x1b[0m%x20%d%s%x20%x1b[33m(%an)%x1b[0m"
      qa2 = "!f() { c=`expr $1 - 1`; git filter-repo --message-callback 'return message + b\"\\nSigned-off-by: Full Name <email>\"' --refs HEAD~$c^..; }; f"

You may need to install git filter-repo first.

git so ####

Runs automated tests, signs off on the patch and attaches it to the bug report, obsoleting the unsigned patch.

git qa ####

Fetches the latest main from git.koha-community.org, creates a new branch off that for the bug you're testing, then applies the patch from Bugzilla

git gr

Not BZ-specific, but this draws a character-mode graph showing the branch structure and displays it through your pager.

git qa2 N

Not BZ-specific, this alias will add your signature to N patches. git qa2 3 => deal with 3 patches