Search code examples
gitgithooksxclip

Git pre-push hook hangs after piping to xclip


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?


Solution

  • 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