Because there is nothing I love more than comparing Git and Mercurial using specific examples, here’s a great big piece of text discussing the design choices that went into these Git commands, and their Mercurial counterparts. Enjoy ;-)
For those who don’t want to read the whole thing, the paragraphs on “See which branches you recently worked on” and “Show changed words instead of lines” probably have the most interesting tidbits.
Leaderboards
Enable the bundled churn extension, and use hg churn, with --changesets to count commits instead of lines touched; -t '{author|email}' to group changesets by the output of a template expression, in this case the committer’s e-mail address; and -r to limit to a subset of revisions.
Something I like about Git’s design: that you can type e.g. 2 weeks and it will interpret it as the date 2 weeks ago. Mercurial has date(-10) for ‘the date range from 10 days ago to now`, but it’s not the same …. Something I like about Mercurial’s design: that grouping and tallying changes has a separate command, rather than being a ‘summary’ option on top of a ‘log’ variant that groups commits (but is called ‘shortlog’ rather than ‘grouplog’)
Praise instead of blame
Defining an alias so you can praise instead of blame: I very much agree, and have indeed got a similar alias in my .hgrc. (In fact, I could have sworn git praise is defined out-of-the-box. Seems it isn’t. Was that SVN, then?)
Something I like about Git’s design: that it lets you set options from the command line. Something I like about SVN’s design: that it includes praise by default. I’m not sure if it’s ironic or appropriate that the distributed VCSes are less sociable, here. :-)
Hide whitespace noise / Show changed words instead of lines
Whitespace: hg diff has a similar set of options.
-w --ignore-all-space ignore white space when comparing lines
-b --ignore-space-change ignore changes in the amount of white space
-B --ignore-blank-lines ignore changes whose lines are all blank
Getting the ten branch heads with the most recent commit date is a job for revsets, of course; printing each one in a special way is a job for output templates.
If you want the output to show commit IDs, dates, and branch names, add -T '{rev} ({date|isodate}): {bookmarks}\n'
Something I like about Mercurial’s design: the clear separation of commit selection (the -r flag plus the revset DLS) and display (the -T flag plus the template DSL). Something I like about Git’s design: this Git command has placeholders with fully spelled-out names. Nice! Somebody should totally port that into git log’s --pretty format DSL.
Also, you know how Mercurial users like to bring up consistency? This is a nice example of the importance of good names and consistent flags.
We have seen 3 log variants so far: log, shortlog, and for-each-ref.
The two commands with ‘log’ in the name are git log and git shortlog.
The two commands with log-like usage are git log and git for-each-ref.
The remaining command, git shortlog, has ‘log’ in its name, but is more interesting as a group-and-tally command: it either prepares changelogs (as its manpage suggests) or tallies churn (as seen in the article).
All three commands have different commit selection flags
All three commands have different formatting DSLs
See what everyone’s been getting up to
hg log + revsets again. And again we need only two flags: -r for commit selection, -T for display formatting.
This is the sort of alias where we start to see bits (a revset, a template, even a template keyword) that we might want to reuse in other places, too. Let’s do that for a lark.
Admirably, Git ships with a one-line template by default. Mercurial doesn’t, though it is easy enough to define one. Admirably, hg log does not hide heads by default. Git’s log does hide heads (and I’ve seen it disorient many people), though it is easy enough to add --all.
Remind yourself what you’ve been up to
This is where we get to reuse the revsets and templates we defined in the previous section.
Check which changes you’re about to pull / Review what you’re about to push
Incoming! Does not require a fetch, either. Equally nifty: its brother, hg outgoing. When pushing/pulling, you can use -r to limit what you would push/pull to the ancestors of certain commits or bookmarks; as a result, hg incoming -r somebookmark works, too.
Something I like about Git’s design: namespaced tracking of last-known remote branch pointer positions was there from the start. I should check out Mercurial’s remotenames extension, though.
Something I like about Mercurial’s design: it has first-class commands for a first-class mental concept. Providing hg incoming and hg outgoing is also very helpful to newbies: pushing and pulling is less scary if you can see for yourself what is about to happen.
Generate a changelog
You can probably guess what is coming: hg log -r ... -T ....
hg log -r '<last tag>::master and not merge()' -T changelog
# git log --oneline --no-merges <last tag>..HEAD
Fancy-schmancy, automatically finding the most recent tag, and ordering by topological branch:
hg log -r 'sort(
last(sort(
tag() and ancestors(master),
"date"
))::. and not merge(),
"topo"
)'
Qua comparing Git and Mercurial, there’s nothing in here we haven’t gone over yet. And whatever your VCS, the most important point isn’t even the specific command you need to use – it’s that you should identify the mental concepts you repeatedly use, and throw them in an alias.
(Come to think of it, every system that has power users – every system that rewards learning – all these systems, be they shells, or TeX; Excel, Vim, Mercurial, or Git – all these systems have aliases or macros, and every power user I know started out by writing macros to make their own lives easier. That, for many, is the first bite of the apple. And the systems reward them richly for it.)
It’s “just” git log -p, but with your default pager overridden to prime less to search for the “commit” label, so you can just hit n to jump through your history commit by commit.
Instead of using various formats for git log, I just install tig. It’s an easy way to pull up and browse recent activity, see what you’re about to push, etc.
Another tip: use -p when adding code that you plan to commit (i.e. git add -p). It will show every hunk of the patch you’re about to add and give you a prompt where you can say ‘y’ or ‘n’ to include it. It basically forces you to do a quick code review (or at least sanity check) prior to committing changes. I’ve caught many a quick & dirty printf this way. It’s also useful for splitting unrelated changes into two distinct commits.
This is a Mercurial trick, but I use it on Git repos via hg-git, so it sorta counts? Maybe? ;-)
# hgrc:
# [revsetalias]
# feature($1) = _firstancestors($1) and not _firstancestors(master)
This shows all commits that were made on a feature branch (branch in the Git sense), whethere they’ve been merged into master or not; and it ignores all commits that were made on the master branch, whether they’ve been merged into feature or not. Like this: only the commits marked ‘F’ for feature get shown.
I prefer rebase-based merging; but git-flow uses explicit merges, and this is really useful for examining that sort of history. It is possible because for each merge commit, Mercurial records which parent was the recipient (HEAD) and which parent was merged in (MERGE_HEAD). I have a strong suspicion that Git, too, stores merge parents in an ordered manner, but I have yet to verify this.
Because there is nothing I love more than comparing Git and Mercurial using specific examples, here’s a great big piece of text discussing the design choices that went into these Git commands, and their Mercurial counterparts. Enjoy ;-)
For those who don’t want to read the whole thing, the paragraphs on “See which branches you recently worked on” and “Show changed words instead of lines” probably have the most interesting tidbits.
LeaderboardsEnable the bundled churn extension, and use
hg churn
, with--changesets
to count commits instead of lines touched;-t '{author|email}'
to group changesets by the output of a template expression, in this case the committer’s e-mail address; and-r
to limit to a subset of revisions.Something I like about Git’s design: that you can type e.g.
Praise instead of blame2 weeks
and it will interpret it as the date 2 weeks ago. Mercurial hasdate(-10)
for ‘the date range from 10 days ago to now`, but it’s not the same …. Something I like about Mercurial’s design: that grouping and tallying changes has a separate command, rather than being a ‘summary’ option on top of a ‘log’ variant that groups commits (but is called ‘shortlog’ rather than ‘grouplog’)Defining an alias so you can
praise
instead ofblame
: I very much agree, and have indeed got a similar alias in my.hgrc
. (In fact, I could have sworngit praise
is defined out-of-the-box. Seems it isn’t. Was that SVN, then?)Something I like about Git’s design: that it lets you set options from the command line. Something I like about SVN’s design: that it includes
Hide whitespace noise / Show changed words instead of linespraise
by default. I’m not sure if it’s ironic or appropriate that the distributed VCSes are less sociable, here. :-)Whitespace:
hg diff
has a similar set of options.Mercurial doesn’t have
See which branches you recently worked on--word-diff
, though. It looks really nice, and I will definitely break out Git (or vimdiff) next time I could use this. Similarly nice: Github’s display, a line-wise diff with word-wise emphasis highlighting.Getting the ten branch heads with the most recent commit date is a job for revsets, of course; printing each one in a special way is a job for output templates.
If you want the output to show commit IDs, dates, and branch names, add
-T '{rev} ({date|isodate}): {bookmarks}\n'
Something I like about Mercurial’s design: the clear separation of commit selection (the
-r
flag plus the revset DLS) and display (the-T
flag plus the template DSL). Something I like about Git’s design: this Git command has placeholders with fully spelled-out names. Nice! Somebody should totally port that intogit log
’s--pretty
format DSL.Also, you know how Mercurial users like to bring up consistency? This is a nice example of the importance of good names and consistent flags.
log
variants so far:log
,shortlog
, andfor-each-ref
.git log
andgit shortlog
.git log
andgit for-each-ref
.git shortlog
, has ‘log’ in its name, but is more interesting as a group-and-tally command: it either prepares changelogs (as its manpage suggests) or tallies churn (as seen in the article).hg log
+ revsets again. And again we need only two flags:-r
for commit selection,-T
for display formatting.This is the sort of alias where we start to see bits (a revset, a template, even a template keyword) that we might want to reuse in other places, too. Let’s do that for a lark.
Admirably, Git ships with a one-line template by default. Mercurial doesn’t, though it is easy enough to define one. Admirably,
Remind yourself what you’ve been up tohg log
does not hide heads by default. Git’s log does hide heads (and I’ve seen it disorient many people), though it is easy enough to add--all
.This is where we get to reuse the revsets and templates we defined in the previous section.
Check which changes you’re about to pull / Review what you’re about to pushIncoming! Does not require a fetch, either. Equally nifty: its brother,
hg outgoing
. When pushing/pulling, you can use-r
to limit what you would push/pull to the ancestors of certain commits or bookmarks; as a result,hg incoming -r somebookmark
works, too.Something I like about Git’s design: namespaced tracking of last-known remote branch pointer positions was there from the start. I should check out Mercurial’s remotenames extension, though.
Something I like about Mercurial’s design: it has first-class commands for a first-class mental concept. Providing
Generate a changeloghg incoming
andhg outgoing
is also very helpful to newbies: pushing and pulling is less scary if you can see for yourself what is about to happen.You can probably guess what is coming:
hg log -r ... -T ...
.Fancy-schmancy, automatically finding the most recent tag, and ordering by topological branch:
Qua comparing Git and Mercurial, there’s nothing in here we haven’t gone over yet. And whatever your VCS, the most important point isn’t even the specific command you need to use – it’s that you should identify the mental concepts you repeatedly use, and throw them in an alias.
(Come to think of it, every system that has power users – every system that rewards learning – all these systems, be they shells, or TeX; Excel, Vim, Mercurial, or Git – all these systems have aliases or macros, and every power user I know started out by writing macros to make their own lives easier. That, for many, is the first bite of the apple. And the systems reward them richly for it.)
This is an awesome post. Please submit it as a separate post or turn it into a blog post and link here?
This post will make @jordigh proud when he sees it.
Can confirm, am proud.
:-)))
git push --force-with-lease
:: probably my favorite example of git doing the “right” thing the wrong way.git reflog
:: for sleuthing after a horrifying rebase mistakegit add -p
) :: really make things feel comfortable.cp -a
:: I often forget these are just files. Don’t forget! If your repo is in a delicate state, just copy the whole damned thing, and try it out.elaborate why?
One of my favorites that I cooked up:
It’s “just”
git log -p
, but with your default pager overridden to primeless
to search for the “commit” label, so you can just hitn
to jump through your history commit by commit.[Comment from banned user removed]
Instead of using various formats for
git log
, I just install tig. It’s an easy way to pull up and browse recent activity, see what you’re about to push, etc.Another tip: use
-p
when adding code that you plan to commit (i.e.git add -p
). It will show every hunk of the patch you’re about to add and give you a prompt where you can say ‘y’ or ‘n’ to include it. It basically forces you to do a quick code review (or at least sanity check) prior to committing changes. I’ve caught many a quick & dirty printf this way. It’s also useful for splitting unrelated changes into two distinct commits.I found this all on the internet, but don’t really remember where.
dotfile
Added images for the git lg and git ll output.
[Comment removed by author]
I stole this alias off of another article posted on lobste.rs:
Share your favorite git tips/aliases if you’ve got some good ones!
This is a Mercurial trick, but I use it on Git repos via hg-git, so it sorta counts? Maybe? ;-)
This shows all commits that were made on a feature branch (branch in the Git sense), whethere they’ve been merged into master or not; and it ignores all commits that were made on the master branch, whether they’ve been merged into feature or not. Like this: only the commits marked ‘F’ for feature get shown.
I prefer rebase-based merging; but git-flow uses explicit merges, and this is really useful for examining that sort of history. It is possible because for each merge commit, Mercurial records which parent was the recipient (
HEAD
) and which parent was merged in (MERGE_HEAD
). I have a strong suspicion that Git, too, stores merge parents in an ordered manner, but I have yet to verify this.Show unpushed commits:
Handy for pipelines:
Overview of the repo’s branch structure: