Thursday, June 23, 2016

Squash several commits in git into one

I had several commits on my branch, all related to the same change. Instead of merging all the commits into master, I wanted to squash all of them into one, and merge the single commit.  Less junk on the master branch that way.

This https://gist.github.com/nicholashagen/2855167 has a shows a very nice way to merge and squash together. But since I use bitbucket(aka stash) and pull requests, the git merge --squash command doesn't work.

I had been looking for a slick way of squashing commits before generating a pull request, and recently found it.

I had 4 commits I wanted to squash (all the ones with the comment "Add sonar jobs for all apps") on the branch feature/add-sonar:

git log shows me the commit IDs


/git/docker-jenkins-master  on  feature/add-sonar * $ git log
commit 02041bddd50de5d5bd40bd3146ae44323fabb6a7
Author: Somaiah Kumbera <somaiah.kumbera@somewhere.com>
Date:   Thu Jun 23 08:50:42 2016 +0200

    Add sonar jobs for all apps

commit 17f11e416982bf486da2db732b3e1de580c59877
Author: Somaiah Kumbera <somaiah.kumbera@somewhere.com>
Date:   Wed Jun 22 14:15:35 2016 +0200

    Add sonar jobs for all apps

commit cc396d605bfe0db279d2beb11833880b3365edc7
Author: Somaiah Kumbera <somaiah.kumbera@somewhere.com>
Date:   Wed Jun 22 13:07:43 2016 +0200

    Add sonar jobs for all apps

commit affcae8432efef916d353dc4f204d4507422eebd
Author: Somaiah Kumbera <somaiah.kumbera@somewhere.com>
Date:   Wed Jun 22 13:04:52 2016 +0200

    Add sonar jobs for all apps

commit 29fa6593a0bc619d269c4b84b8f857b2dbe466e8
Merge: 78f0f8a 9751714
Author: Someone Else <someone.else@somewhere.com>
Date:   Wed Jun 1 10:29:49 2016 +0200

   Something unrelated


git rebase -i allows me to squash the ones from the current point (HEAD) back to the last n commits (in my case n = 4)



/git/docker-jenkins-master  on  feature/add-sonar * $ git rebase -i HEAD~4


At this point I got an editor open up with the list of the last 4 commits, so I could do something with them:



pick affcae8 Add sonar jobs for all apps
pick cc396d6 Add sonar jobs for all apps
pick 17f11e4 Add sonar jobs for all apps
pick 02041bd Add sonar jobs for all apps



Notice that the commits are in reverse chronological order - so the oldest one shows up first. I wanted to pick the oldest one, and squash the others. So I changed the text to look like this:



pick affcae8 Add sonar jobs for all apps
f cc396d6 Add sonar jobs for all apps
f 17f11e4 Add sonar jobs for all apps
f 02041bd Add sonar jobs for all apps


Ctrl-X (to save)
y (to confirm)

The rebase is successful:



[detached HEAD 4d51329] Add sonar jobs for all apps
 2 files changed, 89 insertions(+), 1 deletion(-)
 create mode 100644 path/dir/filename.xml
Successfully rebased and updated refs/heads/feature/add-sonar.



Now all you have to do is push. But you need to force push.



/git/docker-jenkins-master  on  feature/add-sonar * $ git push --force


Now git log shows only 1 commit:



/git/docker-jenkins-master  on  feature/add-sonar  $ git log
commit 4d513295b564ad253c6d738496163c7d392af3d6
Author: Somaiah Kumbera <somaiah.kumbera@somewhere.com>
Date:   Wed Jun 22 13:04:52 2016 +0200

    Add sonar jobs for all apps

commit 29fa6593a0bc619d269c4b84b8f857b2dbe466e8
Merge: 78f0f8a 9751714
Author: Someone Else <someone.else@somewhere.com>
Date:   Wed Jun 1 10:29:49 2016 +0200

   Something unrelated


Now you can merge that single commit in any way you choose :)

Thursday, June 9, 2016

Sonar maven-surefire-plugin quirks

I was integrating my maven multi module project with sonar, and ran into some quirky behavior:

1. After some config changes I suddenly started seeing


Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (SONAR.PROJECTS_KEE) violated

The error may involve org.sonar.db.component.ComponentMapper.insert-Inline

The error occurred while setting parameters

Cause: java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (SONAR.PROJECTS_KEE) violated

Turns out I was calling mvn sonar:sonar with -Dsonar.exclusions and this exclusion was also set on the sonar server, under Administration > General Settings > Analysis Scope > Files as a source file exclusion.

Apparently excluding the same regex on the server and with the maven argument makes Sonar choke.

You can read the full explanation on my stackoverflow question


2. Some packages did not get code coverage with maven-surefire-plugin. Try as I might I always had 0% coverage on these packages. You can view my set up here. The maven output showed that the tests were run, and surefire did not produce any warnings.

It turned out that maven-surefire-plugin was included as a  plugin in the build section in both the parent pom and the child pom. Hence the surefire plugin ran in the child module, but the resultant coverage, which was taken from the file specified by the sonar.jacoco.reportPath property did have these tests. Makes sense now of course, but it was a tricky bug to find :)

Sunday, June 5, 2016

REST API calls with RestTemplate and Basic authorizarion

I was playing around with RestTemplate, and it seems like a nice way to make REST calls. It also allows for easy handling of basic HTTP authorization.

Just add the username and password to the header:

String plainCreds = username + ":" + password;
byte[] base64CredsBytes = Base64.getEncoder().encode(plainCreds.getBytes());
String base64Creds = new String(base64CredsBytes);

HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Creds);

For GET requests thats about all you need. A simple call can be made like this:

HttpEntity<?> requestEntity = new HttpEntity(httpHeaders);
return restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);

POST request are not very much more complicated:

httpHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> requestEntity = new HttpEntity<String>(createIssueJSON, httpHeaders);
return restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);

The entire code looks like this:

import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;

import java.util.Base64;

public class Application {

    private static final String username = "user@domain.com";
    private static final String password = "password";
    private static final String jiraBaseURL = "https://jira.domain.com/rest/api/2/";
    private RestTemplate restTemplate;
    private HttpHeaders httpHeaders;

    public Application() {
        restTemplate = new RestTemplate();
        httpHeaders = createHeadersWithAuthentication();
    }

    private HttpHeaders createHeadersWithAuthentication() {
        String plainCreds = username + ":" + password;
        byte[] base64CredsBytes = Base64.getEncoder().encode(plainCreds.getBytes());
        String base64Creds = new String(base64CredsBytes);

        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Basic " + base64Creds);

        return headers;
    }

    public ResponseEntity getIssue(String issueId) {
        String url = jiraBaseURL + "issue/" + issueId;

        HttpEntity<?> requestEntity = new HttpEntity(httpHeaders);
        return restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
    }

    public ResponseEntity createIssue(String key, String summary, String description, String issueType) {
        String createIssueJSON = createCreateIssueJSON(key, summary, description, issueType);

        String url = jiraBaseURL + "issue";

        httpHeaders.setContentType(MediaType.APPLICATION_JSON);

        HttpEntity<String> requestEntity = new HttpEntity<String>(createIssueJSON, httpHeaders);

        return restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);

    }

    private String createCreateIssueJSON(String key, String summary, String description, String issueType) {
        String createIssueJSON = "{\"fields\":{\"project\":{\"key\":\"$KEY\"},\"summary\":\"$SUMMARY\",\"description\":\"$DESCRIPTION\",\"issuetype\": {\"name\": \"$ISSUETYPE\"}}}";

        createIssueJSON = createIssueJSON.replace("$KEY", key);
        createIssueJSON = createIssueJSON.replace("$SUMMARY", summary);
        createIssueJSON = createIssueJSON.replace("$DESCRIPTION", description);
        return createIssueJSON.replace("$ISSUETYPE", issueType);
    }

}


Download the code and tests from github: https://github.com/somaiah/restTemplate