Posts Tagged ‘git’

Publishing a newly created Git branch to a remote repository can be easier than you might expect.

Introduction

It is a very often situation in various Git workflow models to create a new branch and push (publish) it to a remote repository. Majority of people creates a lot of new branches. Just to initialize a pull (merge) request, to show code to remote workmates or just to backup local changes overnight.

git publish branch

Unfortunately, it is not as easy in Git as it could be:

~/C/my-fancy-project (master|✓) $ git checkout -b featureX
Switched to a new branch 'featureX'

~/C/my-fancy-project (featureX|✓) $ git push
fatal: The current branch featureX has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin featureX

Hmm, just copy-paste the given line and you are set:

~/C/my-fancy-project (featureX|✓) $ git push --set-upstream origin featureX
Total 0 (delta 0), reused 0 (delta 0)
To /tmp/my-fancy-project-remote/
 * [new branch]      featureX -> featureX
Branch 'featureX' set up to track remote branch 'featureX' from 'origin'.

Of course you may memorize it after some time (however, I observe that many people do not) or even use the shorter syntax:

~/C/my-fancy-project (featureX|✓) $ git push -u origin featureX
Total 0 (delta 0), reused 0 (delta 0)
To /tmp/my-fancy-project-remote/
 * [new branch]      featureX -> featureX
Branch 'featureX' set up to track remote branch 'featureX' from 'origin'.

Nonetheless, for me it was to many characters to type, especially repeated multiple times, especially in a typical workflow with one remote repository (usually named origin).

xkcd - Is It Worth the Time?

xkcd – Is It Worth the Time? – https://xkcd.com/1205/

Solution

The perfect solution for me would be just one command. Something like git publish.

~/C/my-fancy-project (master|✓) $ git checkout -b featureY
Switched to a new branch 'featureY'

~/C/my-fancy-project (featureY|✓) $ git publish
Total 0 (delta 0), reused 0 (delta 0)
To /tmp/my-fancy-project-remote/
 * [new branch]      featureY -> featureY
Branch 'featureY' set up to track remote branch 'featureY' from 'origin'.

Would not it be nice?

As you may know from my previous posts, I am a big enthusiast of comprehensive automation (such as CI/CD) or at least semi-automation (aka “making things easier”) when the previous is not possible (or viable). Therefore, at the time, I started looking at possible improvements. Git is written by developers for developers and offers different ways of customization. The easiest is write an alias. In that case is as simple as adding to ~/.gitconfig:

[alias]
    # Gets the current branch name - useful in other commands
    # Git 2.22 (June 2019) introduced "git branch --show-current"
    branch-name = "!git rev-parse --abbrev-ref HEAD"

    # Pushes the current branch to the remote "origin" (or the remote passed as the parameter)
    # and set it to track the upstream branch
    publish = "!sh -c 'git push -u ${1:-origin} $(git branch-name)' -"

As a result in addition to the basic case (seting an upstream branch to origin (if needed) and pushing branches from the current branch to origin):

$ git publish

it is also possible to do publish to some other remote repository:

$ git publish myOtherRemote

Cleaning up

As a counterpart to git publish, it is easy to implement git unpublish:

[alias]
    # Deletes the remote version of the current branch from the remote "origin"
    # (or the remote passed as the parameter)
    unpublish = "!sh -c 'git push ${1:-origin} :$(git branch-name) && git branch --unset-upstream $(git branch-name)' -"

to be remove the current branch from a remote repository (origin or passed as the second parameter):

~/C/my-fancy-project (featureNoLongerNeeded|✓) $ git unpublish
To /tmp/my-fancy-project-remote/
 - [deleted]         featureNoLongerNeeded

instead of:

~/C/my-fancy-project (featureNoLongerNeeded|✓) $ git push origin --delete featureNoLongerNeeded
To /tmp/my-fancy-project-remote/
 - [deleted]         featureNoLongerNeeded

or

~/C/my-fancy-project (featureNoLongerNeeded|✓) $ git push origin :featureNoLongerNeeded
To /tmp/my-fancy-project-remote/
 - [deleted]         featureNoLongerNeeded

Again, shorter and easier to remember.

Partial built-in solution

As proposed by indispensable Łukasz Szczęsny, hassle-free pushing only (without pulling) can be also achieved with Git configuration itself. It may be sufficient having branches removed automatically after a PR is merged (e.g. in properly configured GitLab or GitHub). In that case it is required to set pull.default configuration parameter to current:

~/C/my-fancy-project (master|✓) $ git checkout -b featureZ
Switched to a new branch 'featureZ'

~/C/my-fancy-project (featureZ|✓) $ git push -u
fatal: The current branch featureX has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin featureZ

~/C/my-fancy-project (featureZ|✓) $ git config --global push.default current

~/C/my-fancy-project (featureZ|✓) $ git push -u
Total 0 (delta 0), reused 0 (delta 0)
To /tmp/my-fancy-project-remote/
 * [new branch]      featureZ -> featureZ
Branch 'featureZ' set up to track remote branch 'featureZ' from 'origin'.

~/C/my-fancy-project (featureZ|✓) $ git pull
Already up to date.

Please pay attention to the -u flag in git push -u. It is required to setup remote branch tracking. Without that git pull alone would not work.

Summary

I have been using git publish (and git unpublish) for many years and I really like it. Taking the opportunity of writing this Git Tricks blog series I decided to share it with others (felt in love in a command line :-) ). Remember, however, it is now a part GitKurka (or its uncensored upstream project) – a set of useful and productive tweaks and aliases for Git.

Btw, I do not conduct Git training anymore, but people wanting to develop their Git skills even more may consider an on-site course from Bottega (PL/EN), an online course by Maciej Aniserowicz (devstyle.pl) (PL) or a comprehensive Pro Git book (EN).

Update 20190910. Added partial built-in alternative solution suggested by Łukasz Szczęsny.
Update 20190913. Added missing “branch-name” alias. Pointed out by Paul in a comment.

The lead photo based on the Iva Balk‘s work published in Pixabay, Pixabay License.

Get know how to solve issue with pushing to submodules directly from the main repo while keeping the project easily cloneable by external contributors.

Introduction

The Git submodules mechanism is pretty handy to keep the source code of lousily related dependent software together in one Git repository while leaving their development separate. It is something like symlinks in the Unix world, but with an ability to refer also to the previous version. It’s quite popular in the projects using source code integration (instead of shared libraries) or to speed up development by making related changes in multiple repositories easier. It is not the only possible solution – Gradle, for instance, provides a composite build mechanism. Monorepo is an another approach, but it has its own limitations and it very problematic to use in FOSS projects developed by different people/teams.

As usual, I encountered that situation in one of my projects. As a big enthusiast of automatic code testing and Continuous Delivery, some time ago I have been working on improving the reliability of my (automatically released) gradle-nexus-staging-plugin. After each commit (so also before the release) I wanted to have the end-to-end tests executed to verify that the plugin is able to pass a simple (but real) project through the release process to Maven Central (aka The Central Repository).

I could move that test project to the plugin repository, but – well – it’s a distinct project which could be also released separately or replaced with some other project. In addition, testing two variants of releasing it was handy to use keep it in two branches “mounted” twice in my root repository.

gradle-nexus-staging-plugin
├── src
│   ├── funcTest
│   │   ├── groovy
│   │   │   └── ...
│   │   └── resources
│   │       └── sampleProjects
│   │           ├── nexus-at-minimal (submodule - master branch)
│   │           │   └── ...
│   │           ├── nexus-at-minimal-publish (submodule - publish branch)
│   │           │   └── ...
│   │           └── ...
│   ├── ...

Problem (aka Challenge)

gradle-nexus-staging-plugin contains a submodule nexus-at-minimal from a separate repo. Working on new fancy feature in GNSP it is needed to tweak the acceptance testing project. To make the development smoother it’s very useful to be able to commit introduces changes in the dependent project directly from the working copy of the main project. It works out of the box. However, later on we should also directly push them back to a separate repo of that dependent project (by stepping into the submodule and calling git push or – all-in-once by – using the git push --recurse-submodules=on-deamand parameter when pushing from the main repository). And then – in the long term – we encounter some inconvenience.

Let’s start by defining a submodule path to connect via SSH:

[submodule "src/funcTest/resources/sampleProjects/nexus-at-minimal"]
        path = src/funcTest/resources/sampleProjects/nexus-at-minimal
        url = git@gitlab.com:nexus-at/nexus-at-minimal.git

In general it works fine and pushing is allowed. However, any non-developer trying to clone that repo (and initialize submodules) gets:

...
git@gitlab.com: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

It’s pretty bad in the open source (FOSS) development where the projects are publicly available other people are encouraged to download (clone) it, build and contribute.

The obvious remedy to the previous error is switching to HTTPS:

[submodule "src/funcTest/resources/sampleProjects/nexus-at-minimal"]
        path = src/funcTest/resources/sampleProjects/nexus-at-minimal
        url = https://gitlab.com/nexus-at/nexus-at-minimal.git

However then, to allow developers to commit changes directly to a submodule it is required to go through a separate HTTPS authentication which in the most cases is completely not use in favor of SSH (and would need to be configured independently with an access token).

Transparent solution

To keep external contributors happy the developers could manually change the url in .gitmodules from HTTPS to SSH for every cloned project. However, it’s quite tedious. The better solution is an usage of pushInsteadOf. For the aforementioned example, the developers need only to add to the global ~/.gitconfig configuration file:

[url "git@gitlab.com:nexus-at/"]
    pushInsteadOf = https://gitlab.com/nexus-at/

It effectively overrides the push URL to use SSH instead of HTTPS for the whole group in GitLab/GitHub, covering also submodules (where HTTPS – external contributors friendly scheme – is left as a default).

Simple solution for the same server

As I use GitLab for my profession activity, at that time, I was evaluating its usage also for my FOSS project. Therefor, two different servers (providers) used for the main project and a submodule (here GitHub and GitLab). However, very often it is placed on the same server or even in the same group. In that case the configuration can be simplified even more with relative paths:

[submodule "src/funcTest/resources/sampleProjects/nexus-at-minimal"]
        path = src/funcTest/resources/sampleProjects/nexus-at-minimal
        url = ../nexus-at-minimal.git

The protocol used to clone the main repository (SSH or HTTPS) will be used also for a submodule.

Summary

URL rewriting and conditional configuration (covered in the first part) are just a subset of options available in Git to make the development more flexible and simple. Simple, assuming we already found the required features and learned how to use it ;-).

Update 20190405. Added simple solution with a relative path.

The lead photo based on the Mohamed Hassan‘s work published in Pixabay, Pixabay License.

Have you even committed to Git using wrong email address working on/for different projects/companies? Luckily with a little configuration Git can auto-switch the identities for you.

IMPORTANT. This blog has been archived. You may read an updated version of this post here.
Visit https://blog.solidsoft.pl/ to follow my new articles.

(Too long) introduction and reasoning

Being an (experienced) IT professional can give you an opportunity to work on different things in the same time frame. For instance, in addition to work for the main client, I do some consultancy stuff with code quality & testing and Continuoues Delivery for other companies. What’s more, I also conduct training sessions (will a lot of exercises with code) and work on my own and other’s FOSS projects. Usually, it is convenient to do it on the same computer. It can happen to commit something with the wrong email address (to be detected later on by an external auditor ;-) ) and a push with force to a remote master after rebase is not the best possible solution :-). I started with some Fish-based script to deal with it, but in the end I have found a built-in mechanism in Git itself.

Disclaimer. This particular blog post doesn’t treat about anything new or revolutionary. However, I’ve been living in unawareness long enough to give my blog readers a chance to know that mechanism right now.

superhero-git-identities

Project situation

My ~/Code directory structure could be simplified to something like that:

Code/
├── gradle-nexus-staging-plugin
├── mockito-java8
├── spock-global-unroll
├── ...
├── external-foss
│   ├── ...
│   ├── awaitility
│   └── spock
├── training
│   ├── ...
│   ├── code-testing
│   ├── java11
│   └── jenkins-as-code
└── work
    ├── ...
    ├── codearte
    └── companyX

The goal is to keep an email address in the company’s projects appropriate.

Solution

The first idea which can spring to your mind is to manually override git config user.email "..." in every clonned company repository. However, it’s tedious and error prone (especially in the microservice-based architecture :) ).

Luckily, one of the features introduces in Git 2.13.0 (long time ago – May 2017) is a conditional configuration applying (aka Conditional includes). Armed with it our task is pretty simple.

First, keep your default name and email defined in the [user] section in ~/.gitconfig:

[user]
  name = Marcin Zajączkowski
  email = foss.hacker@mydomain.example.com

Next, create a company related config file in the home directory, e.g. ~/.gitconfig-companyX with just overriden email address:

[user]
  email = marcin.zajaczkowski@companyx.example.com

Glue it together by placing in ~/.gitconfig the following code:

[includeIf "gitdir:~/Code/work/companyX/"]
  path = .gitconfig-companyX

The .gitconfig-companyX is applied only if a current path prefix matches the one defined in includeIf. Simple, yet powerful. That mechanism can be also used to configure different things – such as conditionally using GPG to sign your commits.

Btw, being a paranoid you can even remove the email field from the root configuration to be notified if you forgot to add an email for any new company you colaborate with.

Summary

Thanks to the includeIf mechanism you will never (*) again commit to a repo with wrong name or email. That and other Git related tricks and commands/aliases (such as collected in git-kurka make it easier to focus on delivering the stuff :).

Feel free to leave your favorite Git tips & tricks in the comments.

Update. From the comment made on priv:
– Git works great in that case, but for more generic changes in the environment variables Wybczu suggested very powerful direnv (with support for bash, zsh, tcsh, fish and elvish)

The lead photo based on the Elias Sch‘s work published in Pixabay, Pixabay License.

Last Saturday together with 8 other mentors we were showing various Git-related technics on Git kata event for over 80 people.

Git kata was a free git workshop conducted in a kata form. Paraphrasing Wikipedia “A Git kata is an exercise in using Git which helps an user hone their skills through practice and repetition”. During sessions a mentor was showing selected Git aspects in practice providing listeners detailed comments on each performed step. The attenders could follow master’s steps using their own laptops.

There were various Git technics covered including:
– undoing changes (reset, revert, reflog),
– useful aliases, configuration tricks, Git internals and git prompt with fish shell,
– collaboration with other using public services (like GitHub, Bitbucket, GitLab, Gitorious) or patches via email/USB,
– submodules, filter branch and rerere.

In the past I was leading different programming katas, but I have never head about the idea to use it with Git. It sounded very interesting when I was asked to join a mentors team on Git kata. I hope I helped some people better understand internals of Git commands and in addition having two free slots I got to know (among other things) about rerere which can reduce number of manual merges (have you even heard about it before?). “branch.autosetuprebase always” flag which set a rebase as a default strategy on pull for a newly created branches or a “help.autocorrect 1” flag which automatically applies “did you mean” suggestions in case of typo. The event was very successful and I wonder if not to extend my training portfolio by a Git course.