mailing list mirror (one of many)
 help / Atom feed
From: Stefan Beller <>
To: Patrick Lehmann <>
Cc: Git Mailinglist <>
Subject: Re: Restoring detached HEADs after Git operations
Date: Mon, 19 Jun 2017 09:31:44 -0700
Message-ID: <> (raw)
In-Reply-To: <>

On Mon, Jun 19, 2017 at 1:46 AM, Patrick Lehmann
<> wrote:
> Hello,
> I wrote a Bash script to recover branch names after Git operations have create detached HEADs in a Git repository containing lots of Git submodules. The script works recursively.

Cool. :)

You may also like

These patches are still on my plate, they are not landed yet as I had issues
coming up with a good convincing commit message.

They are essentially putting submodules back on a branch (if configured).
Let's see how this differs from your solution.

> I would like to see:
> a) that script or algorithm being integrated into Git by default

For that you'd want to send a patch, see Documentation/SubmittingPatches.
We'd want to discuss if this command is an independent command
("git submodule reattachHEADs", name subject to bikeshedding ;) )
or if it is a configurable option that is obeyed by anything that touches
submodules (which I would prefer, as this mode seems to be the
"correct default". When having it as a mode we can switch the default
eventually such that submodules are always on a branch).

> b) that as a default behavior for all Git operations creating detached HEADs

changing defaults is hard. Let's go with a) first and then people will
report how
awesome the new mode/command is and then it is easier to see how this
may be a good default. :)

> That's the command:
> --------------------------------
(reformatted for readability:)

git submodule foreach --recursive
  'HEAD=$(git branch --list | head -n 1);
    if [[ "$HEAD" == *HEAD* ]]; then
      REF=$(git rev-parse HEAD);
      for Branch in $(git branch --list | grep "^  " | sed -e "s/  //" );
        if [[ "$(git rev-parse "$Branch")" == $REF ]]; then
          echo -e "  \e[36mCheckout $Branch...\e[0m";
          git checkout $Branch;
      if [[ $FOUND -eq 0 ]]; then
        echo -e "  \e[31mNo matching branch found.\e[0m";
      echo -e "  \e[36mNothing to do.\e[0m";

> --------------------------------
> How does it work:
> 1. It uses git submodule foreach to dive into each Git submodule and execute a series of Bash commands.

If you want to see it upstream eventually, we'd make it shell commands.
There are some subtle differences between shell and bash,
one of them is the way conditions are written. I think plain shell
does not support [[ ]], so that would become

  if test $FOUND -eq 0
    echo ...

Maybe look at for coding style suggestions.

> 2. It's reading the list of branches and checks if the submodule is in detached mode. The first line contains the string HEAD.

This works for you but some crazy person may have a branch containing
HEAD in their branch name. ;)
("git checkout -b notADetachedHEAD")

I think that check can be improved via

    if test $(git symbolic-ref HEAD 2>/dev/null >/dev/null) -eq 128
      # detached HEAD
      # on a branch

so if the output of symbolic-ref starts with ref then it is on a
branch. In detached HEAD

> 3. Retrieve the hash of the detached HEAD
> 4. Iterate all local branches and get their hashes

  What happens (/should happen) when multiple branches have the same sha1?
  With this implementation the first wins? Is this 'lazy guessing' desired?
  The patches referenced above assumed you'd have submodule.NAME.branch
  set and we'd reattach to that branch only (if matching hashes)

> 5. Compare the branch hashes with the detached HEAD's hash. If it matches do a checkout.

Speaking of checkout: checkout --recurse-submodules is a
thing in the latest version of Git, but it also detaches HEADs.

I'd like to have reattaching HEADs in there and then combined with
"git config submodule.recurse true", which is in master but no release
a plain "git checkout <branch>" in the superproject would put the submodules
on branches.

Using checkout within git submodule-foreach works of course just as fine.
Note: Currently Prathamesh Chavan converts git-submodule-foreach to C
so it will be faster. In the process of doing so, we surfaced a couple
of bugs, but
they would not impact this script AFAICT.

> 6. Report if no branch name was found or if a HEAD was not in detached mode.

... and it is colored unconditionally in red. Maybe have a look at
    git config --get-color[bool]
which can help in figuring out if we want to print color codes.

> The Bash code with line breaks and indentation:
> --------------------------------
> HEAD=$(git branch --list | head -n 1)
> if [[ "$HEAD" == *HEAD* ]]; then
>   REF=$(git rev-parse HEAD)
>   FOUND=0
>   for Branch in $(git branch --list | grep "^  " | sed -e "s/  //" ); do
>     if [[ "$(git rev-parse "$Branch")" == $REF ]]; then
>       echo -e "  \e[36mCheckout $Branch...\e[0m"
>       git checkout $Branch
>       FOUND=1
>       break
>     fi
>   done
>   if [[ $FOUND -eq 0 ]]; then
>     echo -e "  \e[31mNo matching branch found.\e[0m"
>   fi
> else
>   echo -e "  \e[36mNothing to do.\e[0m"
> fi
> --------------------------------
> Are their any chances to get it integrated into Git?

I like the idea and I'd be happy to review patches. :)
Also you may want to look at the C version that I provided
above and tell me why yours is better. ;)
(Maybe the chosen defaults are saner, or such?)

> I tried to register that code as a Git alias, but git config complains about quote problem not showing where. It neither specifies if it's a single or double quote problem. Any advice on how to register that piece of code as an alias?

(a) not using the alias system for everything:
* You can define this as a (ba)sh function in e.g. .bashrc and then
just call the shell function from the alias.
* or you can put the code into an executable script "git-NAME" and
then the alias would be just "git submodule foreach --recursive git
(b) define the function inside the alias, cf.

> If wished, I think I could expand the script to also recover hash values to Git tags if no branch was found.

Personally I do not think we should attach a HEAD to a tag in that case.
tags are just like branches with special meaning, i.e. they are also
in the refs/* hierarchy.  Note how git-checkout <tag> detaches from
the tag such that you do not modify the tag by default.

> Kind regards
>     Patrick Lehmann

  parent reply index

Thread overview: 14+ messages in thread (expand / mbox.gz / Atom feed / [top])
2017-06-19  8:46 Patrick Lehmann
2017-06-19  9:30 ` Lars Schneider
2017-06-19  9:52   ` AW: " Patrick Lehmann
2017-06-19 16:37     ` Stefan Beller
2017-06-19 17:34       ` AW: " Patrick Lehmann
2017-06-19 17:47         ` Stefan Beller
2017-06-19 18:09           ` AW: " Patrick Lehmann
2017-06-19 19:21             ` Stefan Beller
2017-06-19 20:13               ` AW: " Patrick Lehmann
2017-06-19 17:55       ` Junio C Hamano
2017-06-19 19:11         ` Stefan Beller
2017-06-19 16:31 ` Stefan Beller [this message]
2017-06-19 17:01 ` Jeff King
2017-06-19 17:56 ` Ævar Arnfjörð Bjarmason

Reply instructions:

You may reply publically to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:

  List information:

* Reply to all the recipients using the --to, --cc,
  and --in-reply-to switches of git-send-email(1):

  git send-email \
    --in-reply-to='' \ \ \ \

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link mailing list mirror (one of many)

Archives are clonable:
	git clone --mirror
	git clone --mirror http://ou63pmih66umazou.onion/git
	git clone --mirror http://czquwvybam4bgbro.onion/git
	git clone --mirror http://hjrcffqmbrq6wope.onion/git

Newsgroups are available over NNTP:

 note: .onion URLs require Tor:
       or Tor2web:

AGPL code for this site: git clone public-inbox