Git pre-push hook hangs after piping to xclip

409 Views Asked by At

Preamble:

When committing I tend to format message as following:

[<task number>] <task title>

which should be converted by pre-push hook to a valid GitHub syntax as following:

Work item [[<git_branch>](http://tracker/_workitems/<git_branch>)]:

- [x] [[<task_number>](http://tracker/_workitems/<task_number>)] <task title>

and then it's catted to console output so i can copy-paste it to PR description on github.

Task

Go one step further and put that message into clipboard so that I don't have to manually select it and copy from console. Since I'm on Linux machine I decided that I'll use xclip for the task.

My current git hook script looks as following:

#!/bin/sh

PBI=\`git symbolic-ref --short HEAD\`

echo "**Backlog Item [$PBI]:**\n" > pr_messages/$PBI.md

git log develop..HEAD --format=" - [x] %B" >> pr_messages/$PBI.md

sed -r -i 's|\[([0-9]{4,})\]|[[\1](http://tracker/_workitems/\1)]|g' pr_messages/$PBI.md

cat pr_messages/$PBI.md

Problem

When I add following line to the end of this script

cat pr_messages/$PBI.md | xclip -selection clipboard

I get the message in my Ctrl+C/V clipboard, but git hangs and I have to abort it. Given that it's supposed to be a pre-push hook, it effectively prevents me from actually pushing my code.

UPD: As proposed by @wumpus-q-wumbley here's the strace output:

$> ps aux | grep git

kraplax  29796  0.0  0.0  25696  5660 pts/1    S+   12:55   0:00 git push
kraplax  29797  0.0  0.0  48276  3040 pts/1    S+   12:55   0:00 ssh [email protected] git-receive-pack 'eduard-sukharev/ProFIT.git'

$> sudo strace -p 29796
Process 29796 attached
wait4(29797, 
^CProcess 29796 detached
 <detached ...>
$> sudo strace -p 29797
Process 29797 attached
select(7, [3 4], [], NULL, NULL
^CProcess 29797 detached
 <detached ...>

Which essentially shows that git-push is waiting for the ssh process ssh [email protected] git-receive-pack 'eduard-sukharev/ProFIT.git' which hangs. This all shifts the focus of the problem a bit.

UPD2: Setting GIT_TRACE=~/git_trace.log gives this info:

$ cat ../git_trace.log 
trace: built-in: git 'rev-parse' '--abbrev-ref' 'HEAD'
trace: built-in: git 'rev-parse' '--abbrev-ref' 'HEAD'
trace: built-in: git 'status' '--porcelain'
trace: built-in: git 'push'
trace: run_command: 'ssh' '[email protected]' 'git-receive-pack '\''eduard-sukharev/ProFIT.git'\'''
trace: run_command: '.git/hooks/pre-push' 'origin' '[email protected]:eduard-sukharev/ProFIT.git'
trace: built-in: git 'symbolic-ref' '--short' 'HEAD'
trace: built-in: git 'log' 'develop..HEAD' '--format= - [x] %B'
trace: built-in: git 'rev-parse' '--abbrev-ref' 'HEAD'
trace: built-in: git 'status' '--porcelain'

Question

Why does this process hang if otherwise it doesn't?

How should I rewrite that line to complete intended task?

Should I probably use other tool for managing clipboard, other than xclip?

2

There are 2 best solutions below

0
On BEST ANSWER

xsel, as well as xclip, waits until another program explicitly fetches the selected data.

This behavior is a design-conditional of X11, since there "is no X selection buffer. The selection mechanism in X11 is an interclient communication mediated by the X server each time any program wishes to know the selection contents [...]. In order to implement modification of the selection(s) (in input, keep and exchange modes) [these programs detach] from the terminal, spawning a child process to supply the new selection(s) on demand. This child exits immediately when any other program takes over the selection(s)" -- from the xsel man-page

With other words your Gits pre-push commit is executed until you start providing your text-selection to the clipboard. The process is then halted until you are making use of this text-snippet by invoking any command or executing any program which "fetches" the clipboard text.

Forking the xclip-command does not work

My first idea was to detach this selection-providing process to let it live parallel to the (then) ongoing execution of your hook-script. Unfortunately this does not work, either the main-process ist halting anyways, until the subprocess returns, or the text-selection of the forked command is not available to the current X11-server.

Possible Solutions

Due to the behavior of the "clipboard" in X11 you must evade providing text-selections in the time-relevant processing of git-hooks.

  1. Use a clipboard-manager - The most clipboard-managers (like Klipper [for KDE] or Glipper [for Gnome]) provide mechanisms to decouple the supply of data from its usage - which emulates the clipboard behavior of Windows and Mac OS.

  2. Alias the git-push-command - if you're operating your git-repository mostly in the shell you might wrap the git-push-command in an alias or small shell-script which first invokes the push and afterwards provides the text-snippet to the clipboard. The disadvantage of this approach is, (besides of the cli-dependency) that the command will "hang" at the end, until you fetched the clipboard-content.

Unless you're not able to install additional software to your system i'd recommend the use of a clipboard-manager.

Use Klipper to put Text from the CLI into the X11-clipboard

You can access Klipper through the shell by using qdbus - a command-line-interface to Qt-applications. An example took from Milian Wolff's Blog Post, adapted to your script, may look like the following:

#!/bin/sh

PBI=\`git symbolic-ref --short HEAD\`
echo "**Backlog Item [$PBI]:**\n" > pr_messages/$PBI.md
git log develop..HEAD --format=" - [x] %B" >> pr_messages/$PBI.md

sed -r -i 's|\[([0-9]{4,})\]|[[\1](http://tracker/_workitems/\1)]|g' pr_messages/$PBI.md

PR_MESSAGE=$(cat pr_messages/$PBI.md)
qdbus org.kde.klipper /klipper setClipboardContents "$PR_MESSAGE"

Another interesting article about the interaction of qdbus and Klipper: https://askubuntu.com/questions/393601/a-command-for-pasting-the-same-as-ctrl-v

1
On

I suspect the xclip background process still has an open file descriptor on the original stdout of the script, preventing git from receiving an EOF on the pipe where it reads the output of the hook. Try adding > /dev/null to the xclip command.