Moving from CVS to Git: $Id$ equivalent?
Asked Answered
D

19

135

I read through a bunch of questions asking about simple source code control tools and Git seemed like a reasonable choice. I have it up and running, and it works well so far. One aspect that I like about CVS is the automatic incrementation of a version number.

I understand that this makes less sense in a distributed repository, but as a developer, I want/need something like this. Let me explain why:

I use Emacs. Periodically I go through and look for new versions of the Lisp source files for third-party packages. Say I've got a file, foo.el, which, according to the header, is version 1.3; if I look up the latest version and see it's 1.143 or 2.6 or whatever, I know I'm pretty far behind.

If instead I see a couple of 40-character hashes, I won't know which is later or get any idea of how much later it is. I would absolutely hate it if I had to manually check ChangeLogs just to get an idea of how out of date I am.

As a developer, I want to extend this courtesy, as I see it, to the people that use my output (and maybe I'm kidding myself that anyone is, but let's leave that aside for a moment). I don't want to have to remember to increment the damn number myself every time, or a timestamp or something like that. That's a real PITA, and I know that from experience.

So what alternatives do I have? If I can't get an $Id:$ equivalent, how else can I provide what I'm looking for?

I should mention that my expectation is that the end user will NOT have Git installed and even if they do, will not have a local repository (indeed, I expect not to make it available that way).

Disbursement answered 21/12, 2008 at 4:45 Comment(0)
A
68

The SHA is just one representation of a version (albeit canonical). The git describe command offers others and does so quite well.

For example, when I run git describe in my master branch of my Java memcached client source, I get this:

2.2-16-gc0cd61a

That says two important things:

  1. There have been exactly 16 commits in this tree since 2.2
  2. The exact source tree can be displayed on anyone else's clone.

Let's say, for example, you packaged a version file with the source (or even rewrote all the content for distribution) to show that number. Let's say that packaged version was 2.2-12-g6c4ae7a (not a release, but a valid version).

You can now see exactly how far behind you are (4 commits), and you can see exactly which 4 commits:

# The RHS of the .. can be origin/master or empty, or whatever you want.
% git log --pretty=format:"%h %an %s" 2.2-12-g6c4ae7a..2.2-16-gc0cd61a
c0cd61a Dustin Sallings More tries to get a timeout.
8c489ff Dustin Sallings Made the timeout test run on every protocol on every bui
fb326d5 Dustin Sallings Added a test for bug 35.
fba04e9 Valeri Felberg Support passing an expiration date into CAS operations.
Ahron answered 22/12, 2008 at 4:3 Comment(2)
Using this will fail as you merge the develop branch, while the master had some hotfixes. The number of commits since the last version will change. The hash is not reliable as someone can rebuild the whole thing with filter-branch or something.Metopic
The write-up only describes learning the information, not embedding it in your executable. For that you need to run the git describe command just before you build, save the output in a header file, or otherwise embed the value in your code.Sachi
H
57

By now there is support for $Id:$ in Git. To enable it for file README you would put "README ident" into .gitattributes. Wildcards on file names are supported. See man gitattributes for details.

Horoscopy answered 16/2, 2011 at 22:21 Comment(2)
This gives you the sha1 of the blob, but not the sha1 of the commit. Useful, just not as an identifier for the commit.Appulse
git doesn't have any keyword expansion mechanism like the $Id$ mentioned. What is stored away is exactly what you get. In any case, the version belongs to the full collection of files making up a commit, not to one file in particular (That idea is a remnant from the RCS days, or perhaps SCCS is to blame here... As CVS is just a glorified frontend to RCS, and SVN tries to be a CVS-workalike, it stuck.).Shrill
E
34

This isn't an unreasonable request from the OP.

My use-case is:

  1. I use Git for my own personal code, therefore no collaboration with others.
  2. I keep system Bash scripts in there which might go into /usr/local/bin when they are ready.

I use three separate machines with the same Git repository on it. It would be nice to know what "version" of the file I have currently in /usr/local/bin without having to do a manual "diff -u <repo version> <version in /usr/local/bin>".

To those of you being negative, remember there are other use cases out there. Not everyone uses Git for collaborative work with the files in the Git repository being their "final" location.

Anyway, the way I did it was to create an attributes file in the repository like this:

cat .git/info/attributes
# see man gitattributes
*.sh ident
*.pl ident
*.cgi ident

Then put $Id$ somewhere in the file (I like to put it after the shebang).

The commit. Note that this doesn't automatically do the expansion like I expected. You have to re-co the file, for example,

git commit foo.sh
rm foo.sh
git co foo.sh

And then you will see the expansion, for example:

$ head foo.sh
#!/bin/sh

# $Id: e184834e6757aac77fd0f71344934b1cd774e6d4 $

Some good information is in How do I enable the ident string for a Git repository?.

Emblaze answered 4/8, 2012 at 9:38 Comment(3)
It's should be noted that this does identify the current file's (blob), not the current commitKandi
What is git co supposed to do? I got the error message "git: 'co' is not a git command. See 'git --help'." Should it be git checkout?Theotheobald
@peter: co is just an alias: e.g. git config --global alias.ci commit; git config --global alias.st status; git config --global alias.co checkout.Lassie
C
23

Not sure this will ever be in Git. To quote Linus:

"The whole notion of keyword substitution is just totally idiotic. It's trivial to do "outside" of the actual content tracking, if you want to have it when doing release trees as tar-balls etc."

It's pretty easy to check the log, though - if you're tracking foo.el's stable branch, you can see what new commits are in the stable branch's log that aren't in your local copy. If you want to simulate CVS's internal version number, you can compare the timestamp of the last commit.

Edit: you should write or use someone else's scripts for this, of course, not do this manually.

Calibre answered 21/12, 2008 at 4:54 Comment(7)
Yeah, I read part of that long string of emails about keyword expansions. Linus' attitude was almost enough to put me off git completely.Disbursement
Yeah, sometimes he’s lacking politeness but he’s usually correct, and he definitely is on the topic of keyword expansion.Selfdetermination
Version tracking is necessary. git is used in so much other infrastructure besides pure development where one can read the code to determine if it makes sense. But when a revision control system is used to track files with arbitrary content then you have to have some means of knowing the official release. git, let alone git log is not on the machine which .ini, .conf, .html and other files were pushed.Borax
@Borax - that is controllable by deploy/package scripts etc. Regardless, contrary to my prediction, git has added support many year ago: git-scm.com/docs/gitattributes#__code_ident_codeCalibre
Linus commented on only one aspect of keyword expansion - release tracking. But it's not the only one purpose of it. That quote clearly demonstrates a man's attitude, but says nothing useful about the subject. A typical "state the obvious in an exalted way" political trick, and the crowd is yours. The problem is that the crowd by definition stupid, because it has only one brain across it all. Which explain the situation with git and keyword expansion clearly. One idiot said "no" and everyone cheered!Anu
@Anu not only that, git now since added support for $Id$ via the ident attribute, as mentioned in another answer here, showing that even git itself isn't hostage to Linus's opinion.Calibre
I know it added the commit checksum, and only it. I still have to go to git command prompt to retrieve anything actually useful out of it.Anu
S
21

As I’ve written before:

Having automatically generated Id tags that show a sensible version number is impossible to do with DSCM tools like Bazaar because everybody’s line of development can be different from all others. So somebody could refer to version “1.41” of a file but your version “1.41” of that file is different.

Basically, $Id$ does not make any sense with Bazaar, Git, and other distributed source code management tools.

Selfdetermination answered 21/12, 2008 at 16:37 Comment(8)
Right, I read that before posting, and that's why I asked for a more general solution to the underlying problem. I think the desire for an individual file to have a version # is legitimate, as is git's inability to provide a solution.Disbursement
It’s not an inability of Git, it’s an inability of all distributed SCMs. If you really want meaningful version numbers, use Subversion, or CVS, or some other centralized system.Selfdetermination
You're right, I should have said "as is DSCM's inability to provide a solution".Disbursement
It's useful to note that bzr has since enabled keyword expansion wiki.bazaar.canonical.com/KeywordExpansionConchiolin
what's wrong with just spitting out the hash instead of a "version number"? I want log statements, debug webpages, and "--version" options on internal scripts that will easily tell me what revision is running where, so I can checkout that specific hash and see why it's behaving the way it is. It simplifies management of deployed apps.... and I don't want some commit hook that considers every commit to be a change to every file that had a $Id$ tag in it.Afterburning
the hash of the file you're working on would work the same as a "version", as long as you can look it up in git by that idSterol
@Brian - per the OP's edit, the end user wants to know the version number but has no access to git or the git logs. In this case, the hash is a meaningless number, not a version number. A DSCM gives no assistance in solving this need.Sachi
maybe that's the correct answer for this particular question, but I still think something like $Id$ showing the hash would be useful to many developers.Afterburning
P
10

I had the same problem. I needed to have a version that was simpler than a hash string and available for people using the tool without needing to connect to the repository.

I did it with a Git pre-commit hook and changed my script to be able to automatically update itself.

I base the version off of the number of commits done. This is a slight race condition because two people could commit at the same time and both think they are committing the same version number, but we don't have many developers on this project.

As an example, I have a script that I checkin that is in Ruby, and I add this code to it - it's pretty simple code so it's easy to port to different languages if you are checking in something in a different language (though obviously this won't easily work with non-runnable checkins such as text files). I've added:

MYVERSION = '1.090'
## Call script to do updateVersion from .git/hooks/pre-commit
def updateVersion
  # We add 1 because the next commit is probably one more - though this is a race
  commits = %x[git log #{$0} | grep '^commit ' | wc -l].to_i + 1
  vers = "1.%0.3d" % commits

  t = File.read($0)
  t.gsub!(/^MYVERSION = '(.*)'$/, "MYVERSION = '#{vers}'")
  bak = $0+'.bak'
  File.open(bak,'w') { |f| f.puts t }
  perm = File.stat($0).mode & 0xfff
  File.rename(bak,$0)
  File.chmod(perm,$0)
  exit
end

And then I add a command-line option (-updateVersion) to the script so if I call it as "tool -updateVersion" then it just calls updateVersion for the tool which modifies the "MYVERSION" value in itself and then exits (you could have it also update other files if they are opened as well if you wanted).

Once that's setup, I go to the Git head and create an executable one-line bash script in .git/hooks/pre-commit.

The script simply changes to the head of the Git directory and calls my script with -updateVersion.

Every time I check in the pre-commit script is run which runs my script with -updateVersion, and then the MYVERSION variable is updated based on what the number of commits will be. Magic!

Picnic answered 12/11, 2012 at 23:25 Comment(2)
So does your Ruby script have to be called updateVersion to have git updateVersion ? Please put some samples of how it is called.Borax
I make an option (-updateVersion) to the script that I'm checking in that calls the 'updateVersion' function (in this case I'm trying to change the version number in the script itself). Then I just make a oneliner shell command that calls my script with -updateVersion, and then it updates itself before every checkin.Picnic
E
9

If having $Keywords$ is essential for you, then maybe you could try to look at Mercurial instead? It has a hgkeyword extension that implement what you want. Mercurial is interesting as a DVCS anyway.

Eigenvalue answered 21/12, 2008 at 10:35 Comment(0)
H
8

Something that is done with Git repositories is to use the tag object. This can be used to tag a commit with any kind of string and can be used to mark versions. You can see that tags in a repository with the git tag command, which returns all the tags.

It's easy to check out a tag. For example, if there is a tag v1.1 you can check that tag out to a branch like this:

git checkout -b v1.1

As it's a top level object, you'll see the whole history to that commit, as well as be able to run diffs, make changes, and merges.

Not only that, but a tag persists, even if the branch that it was on has been deleted without being merged back into the main line.

Horseplay answered 21/12, 2008 at 10:18 Comment(3)
Is there, then, a way to get this tag inserted into the file automatically by git? Thanks!Disbursement
If you mean by keyword expansion? Not as far as I know. if you are building products you can get the information as part of your build script and insert it somewhere into your built product. Try man git-describe which gives the latest tag, the number of commits since this tag, and the current hash.Horseplay
Yes, tags and other related information can now be edited into files automatically by git through the export-subst feature of gitattributes(5). This of course requires use of git archive to create releases, and only in the resulting tar file will the substitution edits be visible.Pyrrhic
C
7

To apply the expansion to all files in all sub-directories in the repository, add a .gitattributes file to the top level directory in the repository (i.e. where you'd normally put the .gitignore file) containing:

* ident

To see this in effect, you'll need to do an effective checkout of the file(s) first, such as deleting or editing them in any way. Then restore them with:

git checkout .

And you should see $Id$ replaced with something like:

$Id: ea701b0bb744c90c620f315e2438bc6b764cdb87 $

From man gitattributes:

ident

When the attribute ident is set for a path, Git replaces $Id$ in the blob object with $Id:, followed by the 40-character hexadecimal blob object name, followed by a dollar sign $ upon checkout. Any byte sequence that begins with $Id: and ends with $ in the worktree file is replaced with $Id$ upon check-in.

This ID will change every time a new version of the file is committed.

Caraviello answered 17/8, 2018 at 10:40 Comment(0)
G
4

If I understand correctly, essentially, you want to know how many commits have happened on a given file since you last updated.

First get the changes in the remote origin, but don't merge them into your master branch:

% git fetch

Then get a log of the changes that have happened on a given file between your master branch and the remote origin/master.

% git log master..origin/master foo.el

This gives you the log messages of all the commits that have happened in the remote repository since you last merged origin/master into your master.

If you just want a count of the changes, pipe it to wc. Say, like this:

% git rev-list master..origin/master foo.el | wc -l
Gabriello answered 21/12, 2008 at 6:14 Comment(1)
So, don't use log: git rev-list master..origin/master | wc -lAhron
A
4

If you're just wanting people to be able to get an idea how far out of date they are, Git can inform them of that in several fairly easy ways. They compare the dates of the last commit on their trunk and your trunk, for example. They can use git cherry to see how many commits have occurred in your trunk that are not present in theirs.

If that's all you want this for, I'd look for a way to provide it without a version number.

Also, I wouldn't bother extending the courtesy to anyone unless you're sure they want it. :)

Appalachian answered 21/12, 2008 at 6:33 Comment(2)
If dates are OK to compare, put the DateTImeStamp in the file. git has so many other use cases than just developers. IT in the field needs to know if the .INI or .conf file on the workstation currently being troubleshootedis anywhere close to what is current.Borax
Will a mere timestamp be enough? The wrong branch can have an appealing timestamp and still be less correct.Upthrust
S
4

If you want the git commit information accessible into your code, then you have to do a pre-build step to get it there. In bash for C/C++ it might look something like this:

prebuild.sh

#!/bin/bash
commit=$(git rev-parse HEAD)
tag=$(git describe --tags --always ${commit})
cat <<EOF >version.c
#include "version.h"
const char* git_tag="${tag}";
const char* git_commit="${commit}";
EOF

with version.h looking like:

#pragma once
const char* git_tag;
const char* git_commit;

Then, wherever you need it in your code #include "version.h" and reference git_tag or git_commit as needed.

And your Makefile might have something like this:

all: package
version:
  ./prebuild.sh
package: version
  # the normal build stuff for your project

This has the benefit of:

  • getting the currently correct values for this build regardless of branching, merging cherry-picking and such.

This implementation of prepublish.sh has the drawbacks of:

  • forcing a recompile even if the git_tag/git_commit didn't change.
  • it does not take into account local modified files that have not been committed but effect the build.
    • use git describe --tags --always --dirty to catch that use-case.
  • pollutes the global namespace.

A fancier prebuild.sh that could avoid these issues is left as an exercise for the reader.

Sachi answered 10/6, 2019 at 23:8 Comment(0)
P
4

Tag names and other related information can now be edited directly into files automatically by Git through the export-subst feature of gitattributes(5). This of course requires use of git archive to create releases, and only in the resulting tar file will the substitution edits be visible.

For example in the .gitattributes file put the following line:

* export-subst

Then in source files you can add a line like this:

#ident  "@(#)PROJECTNAME:FILENAME:$Format:%D:%ci:%cN:%h$"

And it will expand to look like this in a release created by, for example, git archive v1.2.0.90:

#ident  "@(#)PROJECTNAME:FILENAME:HEAD -> master, tag: v1.2.0.90:2020-04-03 18:40:44 -0700:Greg A. Woods:e48f949"
Pyrrhic answered 4/4, 2020 at 2:0 Comment(0)
M
3

RCS IDs are nice for single-file projects, but for any other the $Id$ says nothing about the project (unless you do forced dummy check-ins to a dummy version file).

Still one might be interested how to get the equivalents of $Author$, $Date$, $Revision$, $RCSfile$, etc. on a per file level or at the commit level (how to put them where some keywords are is another question). I don't have an answer on these, but see the requirement to update those, especially when the files (now in Git) originated from RCS-compatible systems (CVS).

Such keywords may be interesting if the sources are distributed separately from any Git repository (that's what I also do). My solution is like this:

Every project has a directory of its own, and in the project root I have a text file named .version which content describes the current version (the name that will be used when exporting the sources).

While working for the next release a script extracts that .version number, some Git version descriptor (like git describe) and a monotonic build number in .build (plus host and date) to an auto-generated source file that is linked to the final program, so you can find out from what source and when it was built.

I develop new features in separate branches, and the first thing I do is add n (for "next") to the .version string (multiple branches originating from the same root would use the same temporary .version number). Before release I decide which branches to merge (hopefully all having the same .version). Before committing the merge, I update .version to the next number (major or minor update, depending on the merged features).

Marked answered 11/1, 2017 at 10:40 Comment(0)
D
2

I agree with those who think that token replacement belongs to build tools rather than to version control tools.

You should have some automated release tool to set the version IDs in your sources at the time the release is being tagged.

Dantedanton answered 10/8, 2016 at 21:17 Comment(2)
.INI .conf and .txt do not usually have a build tool.Borax
But you can hack together a release script that takes the current Git tag and writes it to a file, or something of the sort.Coplin
F
2

I also came from SCCS, RCS, and CVS (%W% %G% %U%).

I had a similar challenge. I wanted to know what version a piece of code was on any system running it. The system may or may not be connected to any network. The system may or may not have Git installed. The system may or may not have the GitHub repository installed on it.

I wanted the same solution for several types of code (.sh, .go, .yml, .xml, etc). I wanted any person without knowledge of Git or GitHub to be able to answer the question "What version are you running?"

So, I wrote what I call a wrapper around a few Git commands. I use it to mark a file with a version number and some information. It solves my challenge. It may help you.

https://github.com/BradleyA/markit

git clone https://github.com/BradleyA/markit
cd markit
Firstfoot answered 13/2, 2018 at 4:57 Comment(0)
H
1

If, like the OP, you use Emacs, you can use its time-stamp function to get the automatic attribute updates you want.

Having your editor do the update has the advantage that the time stamp gets updated when the file contents change, and not when someone merely commits the file to version control or rebuilds.

The time-stamp equivalent of the $Id$ template is Time-stamp: <> and, unlike $Id$, it must occur within the first 8 lines of the file.

You'll want to enable automatic time-stamping in your Emacs init file, which you can do like this:

M-x customize-variable RET
before-save-hook RET

Check the time-stamp box and select Apply and Save.

Hagiography answered 8/4, 2022 at 5:0 Comment(0)
V
0

Since you use Emacs, you might be lucky :)

I've came across this question by coincidence, and also by coincidence I've came by Lively few days ago, an Emacs package which allows having lively pieces of Emacs Lisp in your document. I've not tried it to be honest, but it came to my mind when reading this.

Virginia answered 26/4, 2009 at 18:17 Comment(0)
C
0

To resolve this issue for myself, I created small "hack" as post-commit hook:

echo | tee --append *
git checkout *

In more detail documented in this post on my blog.

Caesarean answered 5/12, 2019 at 16:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.