4

I have several non-bare Git repositories. There is one central Git repository (or at least handled as being the central repo; this might change) but this is also non-bare (because I want to have a checkout on the same machine). My history is mostly linear and I'm the only person who will ever do changes on this repository, so it is unlikely that conflicts will happen. (It is my documents directory.)

Pushing directly into another non-bare repository doesn't work if I use the master branch everywhere. There is receive.denyCurrentBranch which would allow this but it doesn't really help me because (1) it doesn't update the local checkout and (2) I'm afraid of what happens in case there is a conflict.

There are a few related/similar questions here:

I want a solution which is 100% safe. So I think using a post-update hook is no option for me.

I think my use case is not actual that uncommon so I wonder if there are some common solutions for this. Are there?

I think, what I want is:

  • Push local master to some remote special ref (like merge-request-xy) (i.e. like git push origin master:merge-request ?).
  • On the remote, if we can fast-forward and the working copy is clean, fast-forward, update the working copy and delete the ref (basically git merge merge-request && git branch -D merge-request ?).

Would that be safe or a good way to do it?

Community
  • 1
  • 1
Albert
  • 65,406
  • 61
  • 242
  • 386

2 Answers2

2

The solution you have suggested is safe as long as you (1) push to a different branch than master, like merge-request and (2) do the checks about merging. For the second part, fast-forwarding if everything is in the clear on the remote, you can use a post-update hook like this one:

#!/bin/sh

# check args to see if merge-request was pushed,
# and do nothing if it wasn't
if ! $(echo $@ | grep -q 'merge-request');
then
 echo "merge-request not updated"
 exit 0
fi

# cancel if master is not checked out
THIS_BRANCH=$(git branch --no-color | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/')
if [ "$THIS_BRANCH" != "master" ];
then
  echo "master not checked out, not merging"
  exit 1
fi

# cancel if working dir is dirty
if [ $(git status --porcelain | wc -l) != 0 ];
then
  echo "working dir is dirty, not merging"
  exit 1
fi

# try to merge, but only do so if the merge is fast-forward
# try to delete, but only do so if the merge succeeded
git merge --ff-only merge-request && git branch -d merge-request

Make sure to chmod +x your post-update script. When you push, output of this hook will be shown in the console prefaced by "remote:"

In order to push from master into origin/merge-request on the remote, you can either set the push var in your remote config in .git/config (which lets you call git push origin:

[remote "origin"]
    push = +refs/heads/*:refs/merge-request/*

or you can set a repo-specific alias:

[alias]
    push = push origin master:merge-request
shelhamer
  • 29,752
  • 2
  • 30
  • 33
  • Is this really the best way to check if the working dir is clean? This seems fragile. Also, why the `git reset`? Shouldn't the working dir already be up-to-date at this state? Also, if I would place a simple `echo` infront of the `exit 1` fails, wouldn't I see the output of that when I push? – Albert Aug 25 '11 at 18:00
  • @Albert, I have replaced the dirty check with a more robust version (the other check was just something I had on hand from an old script). The `git reset` wasn't supposed to be there. As for the output of a post-update script, I do not believe it is sent back to the pusher–however I have to check that. – shelhamer Aug 25 '11 at 18:09
  • @Albert, updated my script for you. You were right and output of `post-update` is sent back to the pusher–I should have known. – shelhamer Aug 25 '11 at 18:27
  • Another question: How can I only do this if I pushed into the `merge-request` branch? And also I want to ensure that I merge into `master`, i.e. I don't want to update at all if the working copy is in another branch. – Albert Aug 26 '11 at 11:57
  • Ah, and still another question: Is there a way that I can configure the `git push` to automatically merge into `merge-request`? So I don't always have to write the full `git push origin master:merge-request`. – Albert Aug 26 '11 at 11:58
  • For `git push`: The config setting `[remote "origin"]` ... `push = +refs/heads/*:refs/merge-request/*` does kind of what I want. `git push` (when done in `master`) automatically pushes to `refs/merge-request/master`. – Albert Aug 26 '11 at 14:23
  • @Albert, the script now checks out master for merging as its first step. I also added two options for your `push` question, adding the `push` configuration like you discovered, or a repo-specific alias. I'll add the part to only update on push to `merge-request` next. – shelhamer Aug 26 '11 at 15:33
  • @Albert, the script now exits if `merge-request` was not updated in the push. You should edit your question to include what you have commented here because our comment thread is too long. – shelhamer Aug 26 '11 at 16:29
  • I don't want to checkout `master` if I am currently at another branch. I want to break in that case. And I think I don't need the `GIT_DIR` overwrite then (if I don't use `git checkout`), do I? Btw., `echo $@` is not echoing the stdin but the arguments. Also, it might delete the branch even if the previous command failed because it doesn't really check for that. (I would use `&&`.) – Albert Aug 26 '11 at 17:56
  • @Albert, please edit your question with precisely what you want. I can't read your mind. The stdin comment should say args, that is correct behavior. Also, it won't delete the branch because -d refuses to delete unmerged branches. You can add && to feel better about it though. – shelhamer Aug 26 '11 at 18:51
  • P.S. I updated the script for you to check about master being the active branch. This should be what you want! – shelhamer Aug 26 '11 at 19:03
  • I was busy with other stuff so this is the first time I am really trying out the script. The `GIT_DIR` overwrite and `cd ..` is really needed, otherwise the `git status` (and probably also the ff-merge) fails. Also, the branch is always named `merge-request/master`. And also, the `git branch -d merge-request/master` fails for some reason ("`error: branch 'merge-request/master' not found`"), still figuring out why. – Albert Oct 08 '11 at 11:59
0

The dangerous part of pushing to a non-bare repository with a post-update hook is if the remote working copy is unclean and/or you can't do a fast-forward. If you're going to put those pre-conditions on your solution, you might as well go with the better-tested post-update hook with a checkout option.

If you can't guarantee those preconditions, you're going to have to resolve any conflicts from the other end anyway. In that case, you again might as well go with the post-update hook option, but make sure to do the checkout without a --force so if there is a conflict you don't lose local changes.

I still don't see why so many people seem averse to having a bare and non-bare repo on the same machine. I have a bare repo at home that I push to from both home and work. When I start working at either location, I pull into my non-bare repository. It takes 2 seconds, and I'm always in the best physical location to resolve any potential conflicts.

Karl Bielefeldt
  • 47,314
  • 10
  • 60
  • 94