Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moving from CVS to Git: $Id$ equivalent?

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).

like image 215
Joe Casadonte Avatar asked Dec 21 '08 04:12

Joe Casadonte


People also ask

What is the difference between CVS and Git?

The main difference is that (as it was already said in other responses) CVS is (old) centralized version control system, while Git is distributed. But even if you use version control for single developer, on single machine (single account), there are a few differences between Git and CVS: Setting up repository.

What is a git id?

The GitHub ID is basically the ID for your username, nothing else. A commit ID represents a set of changes committed at a certain time by a user, to a specific repository — I don't know what you consider the 'main GIT repository', but all repositories have commits, and each commit has a (unique) commit ID.

What is version control in Git?

Version control, also known as source control, is the practice of tracking and managing changes to software code. Version control systems are software tools that help software teams manage changes to source code over time.


14 Answers

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.
like image 135
Dustin Avatar answered Oct 03 '22 18:10

Dustin


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.

like image 26
Sebastian Pipping Avatar answered Oct 03 '22 19:10

Sebastian Pipping


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?.

like image 25
Imran-UK Avatar answered Oct 03 '22 19:10

Imran-UK


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.

like image 38
orip Avatar answered Oct 03 '22 19:10

orip


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.

like image 42
Bombe Avatar answered Oct 03 '22 19:10

Bombe


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!

like image 22
David Ljung Madison Stellar Avatar answered Oct 03 '22 18:10

David Ljung Madison Stellar


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.

like image 32
Keltia Avatar answered Oct 03 '22 18:10

Keltia


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.

like image 29
Abizern Avatar answered Oct 03 '22 19:10

Abizern


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.

like image 32
Nagev Avatar answered Oct 03 '22 18:10

Nagev


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
like image 37
Otto Avatar answered Oct 03 '22 17:10

Otto


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. :)

like image 4
skiphoppy Avatar answered Oct 03 '22 18:10

skiphoppy


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.

like image 4
Jesse Chisholm Avatar answered Oct 03 '22 19:10

Jesse Chisholm


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"
like image 3
Greg A. Woods Avatar answered Oct 03 '22 19:10

Greg A. Woods


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).

like image 3
U. Windl Avatar answered Oct 03 '22 17:10

U. Windl