简体   繁体   中英

Git: Is there a way to tag remote branch HEAD directly by commit id?

I'm trying to clean up a lot of dead branches in my Git repo, but before I delete any of the branches, I want to make sure I've tagged the HEAD of each branch.

I can retrieve the list of all the remote branches by doing

 git ls-remote --heads origin

This will generate a list of all the commit Ids and branch names.

How can I now create a remote tag for each branch withouth having to checkout each branch locally, create the tag locally and push the tag back to the origin? Do I have to create the tag locally and then push it (ie: a 2 step process)?

Ex:

git tag <branch_name_as_tag> <commitId>
git push origin <branch_name_as_tag>

Is there not a cleaner way of doing this?

No real need to mess with local tags, just do the rewrite while pushing.

git push origin refs/remotes/origin/*:refs/tags/tagged-*

If you want the tags local, push to your own repo (by pathname, with . ).

The short answer is "maybe"—or "yes" if you really mean what you put in your example—which is admittedly entirely unhelpful.

The complete answer

It may help if you realize that the way git push works is:

  1. call up the remote;
  2. send it any commits and other objects required, if any;
  3. ask it to set some specific references to some specific object IDs;
  4. read its replies to step 3 and use them to update local references.

On top of this, there are two "kinds" of tags, and if you haven't seen this before, we must define "reference".

In Git, a reference is just an almost-arbitrary 1 string beginning with refs/ . There are, however, several distinguished prefixes that form a hierarchical name space of references. Branch names start with refs/heads/ , tag names start with refs/tags/ , and remote-tracking branch names start with refs/remotes/ and have one more level to name the specific remote (usually origin/ ).

The two kinds of tags are lightweight and annotated tags. The key difference is that an annotated tag has an associated repository object. (This may need defining "object", but I'll leave that to a separate question. For now let's just say the annotated-tag object has the commit hash in it, along with the name of the person making the tag, and any PGP signature. This tag object has its own separate hash ID, just like any commit hash.) If a tag reference—a name starting with refs/tags/ —names an annotated tag object, it's an annotated tag. Otherwise it's a lightweight tag.

So, when your Git calls up the other Git, the most interesting part is step 3. Your Git will say: set refs/tags/v1.2 to point to object #face0ff... . The crucial question is: where does this number, face0ff or whatever, come from?

If you wish to tag a commit that is already in the upstream, the number comes from their Git repository. Now, to get your Git to see it, that number must also be in your Git repository when you start, but that's easy to do:

git fetch

or:

git fetch origin

and now you have all the branches from origin , in your own refs/remotes/origin/* name-space, with all their commit IDs, and all their commits are safely stored in your repository.

But, if you want these new tags to be annotated tags , you will need annotated-tag objects . You will most likely have to create new ones. Each new annotated tag object should point to an existing commit. Then you can run the same git push as before, but this time, in step 2, have your Git send their Git these new annotated-tag objects. Then your step 3 consists of asking them to set their refs/tags/v1.2 to the IDs of your annotated tag objects, which you just sent them during step 2 (they use the same IDs).

Your example, git tag newtag <commitid> , makes lightweight tags (to commits you already have), so you don't need to create new tag objects. In this case, since you presumably already have all the commit IDs:

git push 1234567:refs/tags/newtag

You can list multiple instances of this on the command line.

However, it may be simpler, not to mention a whole lot less error-prone, to do this instead:

git tag <name> <id>
git tag <name> <id>
  ...
git tag <name> <id>
git push --tags origin

The last step tells your Git to push your refs/tags/* to their refs/tags/* , where * simply matches everything (including embedded slashes, so it's not quite the same as shell glob * ). The tags they already have, they'll skip in step 3 (because there's no change to make) or reject in step 3 (because changes to tags are disallowed by default).


1 There are a bunch of rules keeping these from being arbitrary. See the git-check-ref-format documentation for details. Note that the only standard one-level reference is refs/stash , for "the stash".

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM