0

I have a large series of commits, with various version tags, i.e.:

root->x->x->1->x->x->x->2->x->x->3->x->4->x->x->x->5->master

I want to insert a new commit near the start of this. The problem is, I then need to go through and re-tag everything, because my tree ends up looking like this:

root->x->x->y->x->x->x->x->x->x->x->x->x->x->x->x->x->x->master
         \->1->x->x->x->2->x->x->3->x->4->x->x->x->5

Is there a way I can tag a specific commit, rather than a specific hash? So that when I do a rebase, the tags apply to the same specific commits, in the new tree, rather than the original commits?

Note: I am not interested in moralizing - please don't post any answers saying things like "you're doing it wrong" or "that's not how versioning is supposed to work" unless you can also provide a constructive alternative. I need a way to mark commits that will persist after a rebase, even if the only possible solution is "Git doesn't do that, use X instead".

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Benubird
  • 18,551
  • 27
  • 90
  • 141
  • 1
    I'll post this as a comment only because you don't want such answers. Git doesn't work like that. Commits in Git have cryptographic integrity. You cannot change their parent, author, message or patch without affecting the hash. Recognizing one commit is the same as another based on the patch would be a job for an external tool/command. – Haralan Dobrev Mar 10 '14 at 10:34
  • You question title itself reveals a deep misunderstanding of how git works. The hash *is* the commit, for all intents and purposes. Ultimately, you can't unambiguously identify a commit in any other way. All other means of specifying a commit get resolved into a hash under the hood. – Wildcard Mar 17 '16 at 08:20
  • @HaralanDobrev But when I do a rebase, git recalculates all the hashes. Can it not then detect that the hash has changed, and move the associated tags to point to the new commit? – Benubird Mar 17 '16 at 08:55
  • @Benubird, inserting a commit is just *one* of the many things you can do with rebase. You can reword commit messages, edit the commit's changes directly, squash multiple commits together into one (and reword the mesage if you like), reorder the commits, omit certain commits entirely, add entirely new commits...in most of these cases there is no automatic way whatsoever to tell which of the "new" commits corresponds to the "old" commit. – Wildcard Mar 17 '16 at 09:01
  • The point being, the hashes aren't just "recalculated"—rather, hashes are *newly* calculated, for the *new* commits you are creating, which (internally) have nothing whatsoever to do with the old commits you are basing them upon. I do get your question, but the entire purpose of tags is to be reliable *static* pointers to a specific revision. However see my answer for a way to "pseudo-tag" commits in a way that will carry on past a rebase. – Wildcard Mar 17 '16 at 09:04

2 Answers2

2

Here is an alternative solution which I've seen many repositories use.

  • Create a branch for every major version you want to maintain.
  • Push fixes/updates to every branches. Use merging/rebasing/cherry-picking between them when possible to avoid repetitive work.
  • Tag commits in those branches with minor/patch versions.

Example:

  • You have a master branch.
  • You release version 1.0.0 pointing to the current master.
  • You continue to push to master and release a 1.1.0 version.
  • You then make a major refactor/backwards incompatible changes and release 2.0.0 version.
  • You could (and should) then make a 1.x branch pointing to the previous 1.1.0 version.
  • You then push a bug fix (in master) which affects both 2.x and 1.x versions.
  • Tag the commit as 2.0.1. Cherry-pick it in 1.x branch and tag that as 1.1.1.

There are variations to this workflow. You could always have a branch for your major versions and the master branch could be your active development branch for your next major version.

I think this is solving your problem by going deeper and addressing the root cause of your workflow which is - pushing a change to several versions.


I should also mention that changing what tags point to after you've pushed the tags is always bad. Very bad.

Haralan Dobrev
  • 7,617
  • 2
  • 48
  • 66
  • 1
    This is the solution we took when moving from clearcase to git. In clearcase, it is standard practice to move tags (actually labels). Branching is what Git has to offer for close-enough functionality. – eis Mar 10 '14 at 10:47
  • I just wrote an alternate answer; your answer is better for production use but `LESS='+/:/' man gitrevisions` gets short shrift. :) – Wildcard Mar 17 '16 at 08:46
1

...please don't post any answers saying things like "you're doing it wrong" or "that's not how versioning is supposed to work" unless you can also provide a constructive alternative. I need a way to mark commits that will persist after a rebase...

You're doing it wrong; that's not how versioning is supposed to work. Now for my constructive alternative—I do have one, and it's not "Git doesn't do this."

Using "tags" is out. If you do use git tags, even if you hack together some scripts that will delete the old tags and create new ones every time you rebase, your new tags won't be pulled by any other users collaborating with you, making the tags worse than useless as they would be actively misleading. (See LESS=+/DISCUSSION man git-tag for more info about this.)

However, there is a clean and elegant way to accomplish what you intend, without the use of tags. Note that it will not allow you to "pseudo-tag" a commit after the fact without rebasing to modify that commit, but since you're rebasing often already, I believe it will be perfect for your workflow.

The key to my pseudo-tag method is an understanding of a seldom-used method of specifying a git revision. See LESS='+/:/<text>' man gitrevisions to read up on it.

Decide on a text string that is short and won't show up in a commit message accidentally, and use it as a prefix for your pseudo-tags. For example, my-pseudo-tag-.

Then, for any commit which you want to pseudo-tag, modify that commit message (using rebase) to include the string, e.g., my-pseudo-tag-v0.7.5.

Now any time you want to refer to that pseudo-tag, e.g. to check out that version, or to create a new branch pointing there, use the git revision specifier like so:

git checkout :/my-pseudo-tag-v0.7.5
git checkout -b new_branch :/my-pseudo-tag-v1.3.0

Voila! Since your pseudo-tags are actually part of the commit message rather than a separate object pointing to the commit, they are unaffected by a rebase and can still be found only in the commit message where you expect to find them!

In other words, the way to mark commits that will persist after a rebase, is to include specific text in the commit messages that will only be found in that commit's message.

(Of course, if you are working with other people, you will have to dream up some way to ensure that they don't use the "reserved word" in their own commit messages...but that's beyond the scope of your question and my solution.)


Note: Please don't rebase commits that are available to others; it will make for future headaches. (This is directed at any future readers, not the Original Poster of this question whose workflow evidently demands rebasing.)

Wildcard
  • 1,302
  • 2
  • 20
  • 42