Why not Git?
Git is a very good tool for advanced users but it is really too complicated and unsafe for what we need as scientists / students / teachers / developers of most scientific programs.
Mercurial is more adapted for us in academics.
simpler and safer for beginners,
very powerful for advanced users (it is used for example at Facebook and Mozilla... and for PyPy repository).
For advanced Git users, when using Mercurial, you loose the Git index (but not what you usually do with it) and you gain very interesting features, like for example long-term named branches, safe distributed history edition (phases, obsolete changesets, evolve extension) and the command hg absorb
(automatic injection of uncommitted changes into prior commits in a PR).
https://gricad-gitlab.univ-grenoble-alpes.fr
For now, we'll have to use a Mercurial extension (hg-git) and it won't be as nice as if we could use a platform natively supporting Mercurial.
But there is Heptapod (Gitlab with Mercurial support)!
help
¶hg help
Mercurial Distributed SCM list of commands: Repository creation: clone make a copy of an existing repository init create a new repository in the given directory Remote repository management: incoming show new changesets found in source outgoing show changesets not found in the destination paths show aliases for remote repositories pull pull changes from the specified source push push changes to the specified destination serve start stand-alone webserver Change creation: absorb incorporate corrections into the stack of draft changesets commit commit the specified files or all outstanding changes Change manipulation: backout reverse effect of earlier changeset graft copy changes from other branches onto the current branch merge merge another revision into working directory rebase move changeset (and descendants) to a different branch Change organization: bookmarks create a new bookmark or list existing bookmarks branch set or show the current branch name branches list repository named branches phase set or show the current phase name tag add one or more tags for the current or given revision tags list repository tags File content management: annotate show changeset information by line for each file cat output the current or given revision of files copy mark files as copied for the next commit diff diff repository (or selected files) extdiff use external program to diff repository (or selected files) grep search revision history for a pattern in specified files meld use external program to diff repository (or selected files) Change navigation: bisect subdivision search of changesets heads show branch heads identify identify the working directory or specified revision log show revision history of entire repository or files Working directory management: add add the specified files on the next commit addremove add all new files, delete all missing files files list tracked files forget forget the specified files on the next commit remove remove the specified files on the next commit rename rename files; equivalent of copy + remove resolve redo merges or set/view the merge status of files revert restore files to their checkout state root print the root (top) of the current working directory shelve save and set aside changes from the working directory status show changed files in the working directory summary summarize working directory state unshelve restore a shelved change to the working directory update update working directory (or switch revisions) Change import/export: archive create an unversioned archive of a repository revision bundle create a bundle file export dump the header and diffs for one or more changesets import import an ordered set of patches unbundle apply one or more bundle files Repository maintenance: churn histogram of changes to the repository manifest output the current or given revision of the project manifest recover roll back an interrupted transaction verify verify the integrity of the repository Help: config show combined config settings from all hgrc files help show help for a given topic or a help overview version output version and copyright information Uncategorized commands: amend combine a changeset with updates and replace it with a new one evolve solve troubled changesets in your repository fold fold multiple revisions into a single one gclear clear out the Git cached data gexport export commits from Mercurial to Git gimport import commits from Git to Mercurial git-cleanup clean up Git commit map after history editing gverify verify that a Mercurial rev matches the corresponding Git rev metaedit edit commit information next update to next child revision obslog show the obsolescence history of the specified revisions pdiff show diff combining committed and uncommited changes pick move a commit on the top of working directory parent and updates to it. previous update to parent revision prune mark changesets as obsolete or succeeded by another changeset pstatus show status combining committed and uncommited changes rewind rewind a stack of changesets to a previous state split split a changeset into smaller changesets stack list all changesets in a topic and other information topics View current topic, set current topic, change topic for a set of revisions, or see all topics. touch create successors identical to their predecessors but the changeset ID uncommit move changes from parent revision to working directory enabled extensions: churn command to display statistics about repository history evolve extends Mercurial feature related to Changeset Evolution extdiff command to allow external programs to compare revisions hggit push and pull from a Git server rebase command to move sets of revisions to a different ancestor shelve save and restore changes to the working directory topic support for topic branches additional help topics: Mercurial identifiers: filesets Specifying File Sets hgignore Syntax for Mercurial Ignore Files patterns File Name Patterns revisions Specifying Revisions urls URL Paths Mercurial output: color Colorizing Outputs dates Date Formats diffs Diff Formats templating Template Usage Mercurial configuration: config Configuration Files environment Environment Variables extensions Using Additional Features flags Command-line flags hgweb Configuring hgweb merge-tools Merge Tools pager Pager Support Concepts: bundlespec Bundle File Formats glossary Glossary phases Working with Phases subrepos Subrepositories Miscellaneous: deprecated Deprecated Features internals Technical implementation topics scripting Using Mercurial from scripts and automation Uncategorized topics: evolution Safely Rewriting History git Working with Git Repositories (use 'hg help -v' to show built-in aliases and global options)
hg help init
hg init [-e CMD] [--remotecmd CMD] [DEST] create a new repository in the given directory Initialize a new repository in the given directory. If the given directory does not exist, it will be created. If no directory is given, the current directory is used. It is possible to specify an "ssh://" URL as the destination. See 'hg help urls' for more information. Returns 0 on success. options: -e --ssh CMD specify ssh command to use --remotecmd CMD specify hg command to run on the remote side --insecure do not verify server certificate (ignoring web.cacerts config) (some details hidden, use --verbose to show complete help)
init
, status
, add
, remove
, addremove
, commit
, log
and summary
¶# cleanup
rm -rf /tmp/myrepos
mkdir -p /tmp/myrepos/myrepo0
cd /tmp/myrepos/myrepo0
touch file0.txt
hg init
hg status
? file0.txt
# equivalent command (shorter)
hg st
? file0.txt
hg add
adding file0.txt
hg st
A file0.txt
hg commit -m "First commit"
hg st
touch file1.txt
hg st
? file1.txt
rm -f file0.txt
hg st
! file0.txt ? file1.txt
hg addremove
# or
# hg addre
removing file0.txt adding file1.txt
hg st
A file1.txt R file0.txt
hg commit -m "Remove file0 and add file1"
hg st
ls
file1.txt
Let's modify the file file1.txt
:
echo "Hello world!" >> file1.txt
hg st
M file1.txt
There is no "Git index" in Mercurial. A modification in a versioned file is automatically included in the next commit.
We can really think about the commits as "snapshots of the code".
hg commit -m "Hello world in file1.txt"
hg st
hg sum
parent: 2:f64ab784311c tip
Hello world in file1.txt
branch: default
commit: (clean)
update: (current)
phases: 3 draft
Mercurial tells us that we are in the branch "default", which is... the default "named branch". Note that Mercurial "named branches" are used only for long term features or versions of software (for example "pypy2.7" and "pypy3.6" in Pypy repository) and not for feature branches like branches in Git. For this tutorial, we won't use other named branches and we'll always work in the default branch.
hg log
changeset: 2:f64ab784311c tag: tip user: paugier <pierre.augier@univ-grenoble-alpes.fr> date: Fri Jun 14 23:20:17 2019 +0200 summary: Hello world in file1.txt changeset: 1:ebb8c645f614 user: paugier <pierre.augier@univ-grenoble-alpes.fr> date: Fri Jun 14 23:20:16 2019 +0200 summary: Remove file0 and add file1 changeset: 0:29f68d89d843 user: paugier <pierre.augier@univ-grenoble-alpes.fr> date: Fri Jun 14 23:20:14 2019 +0200 summary: First commit
hg log --graph
@ changeset: 2:f64ab784311c | tag: tip | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:17 2019 +0200 | summary: Hello world in file1.txt | o changeset: 1:ebb8c645f614 | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:16 2019 +0200 | summary: Remove file0 and add file1 | o changeset: 0:29f68d89d843 user: paugier <pierre.augier@univ-grenoble-alpes.fr> date: Fri Jun 14 23:20:14 2019 +0200 summary: First commit
hg log -G
@ changeset: 2:f64ab784311c | tag: tip | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:17 2019 +0200 | summary: Hello world in file1.txt | o changeset: 1:ebb8c645f614 | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:16 2019 +0200 | summary: Remove file0 and add file1 | o changeset: 0:29f68d89d843 user: paugier <pierre.augier@univ-grenoble-alpes.fr> date: Fri Jun 14 23:20:14 2019 +0200 summary: First commit
revert
¶echo file1.txt
file1.txt
echo "Something wrong" > file1.txt
hg st
M file1.txt
hg revert file1.txt
# or to revert all files:
# hg revert --all
ls
file1.txt file1.txt.orig
hg st
? file1.txt.orig
rm -f file1.txt.orig
update
¶hg log -G
@ changeset: 2:f64ab784311c | tag: tip | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:17 2019 +0200 | summary: Hello world in file1.txt | o changeset: 1:ebb8c645f614 | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:16 2019 +0200 | summary: Remove file0 and add file1 | o changeset: 0:29f68d89d843 user: paugier <pierre.augier@univ-grenoble-alpes.fr> date: Fri Jun 14 23:20:14 2019 +0200 summary: First commit
hg update 0
# `update` or just `up`
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
hg sum
parent: 0:29f68d89d843
First commit
branch: default
commit: (clean)
update: 2 new changesets (update)
phases: 3 draft
ls
file0.txt
echo "A line in file0.txt" >> file0.txt
hg st
M file0.txt
hg commit -m "A line in file0.txt"
created new head (consider using topic for lightweight branches. See 'hg help topic')
We were at commit 0 and we commited. It created a (unnamed) branch.
hg log -G
@ changeset: 3:674ce551f5c4 | tag: tip | parent: 0:29f68d89d843 | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:21 2019 +0200 | summary: A line in file0.txt | | o changeset: 2:f64ab784311c | | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | | date: Fri Jun 14 23:20:17 2019 +0200 | | summary: Hello world in file1.txt | | | o changeset: 1:ebb8c645f614 |/ user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:16 2019 +0200 | summary: Remove file0 and add file1 | o changeset: 0:29f68d89d843 user: paugier <pierre.augier@univ-grenoble-alpes.fr> date: Fri Jun 14 23:20:14 2019 +0200 summary: First commit
hg up 1
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
hg sum
parent: 1:ebb8c645f614
Remove file0 and add file1
branch: default
commit: (clean)
update: 2 new changesets (update)
phases: 4 draft
hg up
1 files updated, 0 files merged, 0 files removed, 0 files unresolved updated to "f64ab784311c: Hello world in file1.txt" 1 other heads for branch "default"
hg sum
parent: 2:f64ab784311c
Hello world in file1.txt
branch: default
commit: (clean)
update: 1 new changesets, 2 branch heads (merge)
phases: 4 draft
ls
file1.txt
touch file2.txt
hg st
? file2.txt
hg add
adding file2.txt
hg commit -m "Add file 2"
hg log -G
@ changeset: 4:5f1ccf526f78 | tag: tip | parent: 2:f64ab784311c | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:23 2019 +0200 | summary: Add file 2 | | o changeset: 3:674ce551f5c4 | | parent: 0:29f68d89d843 | | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | | date: Fri Jun 14 23:20:21 2019 +0200 | | summary: A line in file0.txt | | o | changeset: 2:f64ab784311c | | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | | date: Fri Jun 14 23:20:17 2019 +0200 | | summary: Hello world in file1.txt | | o | changeset: 1:ebb8c645f614 |/ user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:16 2019 +0200 | summary: Remove file0 and add file1 | o changeset: 0:29f68d89d843 user: paugier <pierre.augier@univ-grenoble-alpes.fr> date: Fri Jun 14 23:20:14 2019 +0200 summary: First commit
We cleanup a bit to get back a linear history. Since all commits are local, they are in a draft phase and they can be obsoleted without problem.
hg prune 3
1 changesets pruned
hg log -G
@ changeset: 4:5f1ccf526f78 | tag: tip | parent: 2:f64ab784311c | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:23 2019 +0200 | summary: Add file 2 | o changeset: 2:f64ab784311c | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:17 2019 +0200 | summary: Hello world in file1.txt | o changeset: 1:ebb8c645f614 | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:16 2019 +0200 | summary: Remove file0 and add file1 | o changeset: 0:29f68d89d843 user: paugier <pierre.augier@univ-grenoble-alpes.fr> date: Fri Jun 14 23:20:14 2019 +0200 summary: First commit
clone
, push
, pull
¶cd ..
pwd
/tmp/myrepos
hg clone myrepo0 myrepo_web
requesting all changes adding changesets adding manifests adding file changes added 4 changesets with 4 changes to 3 files 1 new obsolescence markers new changesets 29f68d89d843:5f1ccf526f78 updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
cd myrepo0
ls
file1.txt file2.txt
echo "Hello" > file2.txt
hg st
M file2.txt
hg commit -m "Improve file2"
hg log -G
@ changeset: 5:29ce9b90e420 | tag: tip | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:25 2019 +0200 | summary: Improve file2 | o changeset: 4:5f1ccf526f78 | parent: 2:f64ab784311c | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:23 2019 +0200 | summary: Add file 2 | o changeset: 2:f64ab784311c | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:17 2019 +0200 | summary: Hello world in file1.txt | o changeset: 1:ebb8c645f614 | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:16 2019 +0200 | summary: Remove file0 and add file1 | o changeset: 0:29f68d89d843 user: paugier <pierre.augier@univ-grenoble-alpes.fr> date: Fri Jun 14 23:20:14 2019 +0200 summary: First commit
hg push /tmp/myrepos/myrepo_web
pushing to /tmp/myrepos/myrepo_web searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files
At this point, we have 2 repositories.
cd /tmp/myrepos
pwd
ls
/tmp/myrepos myrepo0 myrepo_web
Let's create a new repository from the repo on the web. (Usually, it is done in another computer.)
hg clone myrepo_web myrepo_other_computer
updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
cd myrepo_other_computer
ls
file1.txt file2.txt
cat file2.txt
Hello
echo "Hello Word!" > file2.txt
hg st
M file2.txt
hg commit -m '"Hello Word!" is even nicer'
hg st
We push the new changeset towards the default repository (from which this repository has been created), which is the "web" repository.
hg push
pushing to /tmp/myrepos/myrepo_web searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files
Let's come back to the original repository in the first computer.
cd ../myrepo0
cat file2.txt
Hello
hg sum
parent: 5:29ce9b90e420 tip
Improve file2
branch: default
commit: (clean)
update: (current)
hg pull /tmp/myrepos/myrepo_web
pulling from /tmp/myrepos/myrepo_web searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files new changesets f2242950a7e7 (run 'hg update' to get a working copy)
hg up
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
We could have just run hg pull -u
to pull and update to the tip of the branch where we are, i.e. the last commit since the repository is now in a linear state.
hg sum
parent: 6:f2242950a7e7 tip
"Hello Word!" is even nicer
branch: default
commit: (clean)
update: (current)
cat file2.txt
Hello Word!
merge
command¶We can now consider the case in which 2 persons (Alice and Bob) work at the same time on the same project. First they both clone the web repository.
# Alice
cd /tmp/myrepos/
hg clone myrepo_web myrepo_Alice
updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
# Bob
hg clone myrepo_web myrepo_Bob
updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
Just for this notebook, we can tell Mercurial who is the owner of Alice's and Bob's repositories.
# not useful in real life!
echo "username = Alice <alice@mail.wonderland>" >> /tmp/myrepos/myrepo_Alice/.hg/hgrc
echo "username = Bob <bob@mail.wonderland>" >> /tmp/myrepos/myrepo_Bob/.hg/hgrc
# Alice works
cd /tmp/myrepos/myrepo_Alice
touch Alice_file.py
hg add
hg commit -m "Alice adds a file"
hg push
adding Alice_file.py
pushing to /tmp/myrepos/myrepo_web
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
# Bob works
cd /tmp/myrepos/myrepo_Bob
touch Bob_file.py
hg add
hg commit -m "Bob adds a file"
adding Bob_file.py
At this point, hg push
would lead to the following error:
pushing to /tmp/myrepos/myrepo_web
searching for changes
remote has heads on branch 'default' that are not known locally: 69b3f39e70ab
abort: push creates new remote head 938fc2084593!
(pull and merge or see 'hg help push' for details about pushing new heads)
Bob encounters a problem... The remote repository has a commit which is not know locally. Let's first pull this commit.
hg pull
pulling from /tmp/myrepos/myrepo_web searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) new changesets 249808fd5597 (run 'hg heads' to see heads, 'hg merge' to merge)
Now we can examine the situation.
hg heads
changeset: 7:249808fd5597 tag: tip parent: 5:f2242950a7e7 user: Alice <alice@mail.wonderland> date: Fri Jun 14 23:20:30 2019 +0200 summary: Alice adds a file changeset: 6:f229009f4e26 user: Bob <bob@mail.wonderland> date: Fri Jun 14 23:20:31 2019 +0200 summary: Bob adds a file
hg log -G -l 4
o changeset: 7:249808fd5597 | tag: tip | parent: 5:f2242950a7e7 | user: Alice <alice@mail.wonderland> | date: Fri Jun 14 23:20:30 2019 +0200 | summary: Alice adds a file | | @ changeset: 6:f229009f4e26 |/ user: Bob <bob@mail.wonderland> | date: Fri Jun 14 23:20:31 2019 +0200 | summary: Bob adds a file | o changeset: 5:f2242950a7e7 | user: paugier <pierre.augier@univ-grenoble-alpes.fr> | date: Fri Jun 14 23:20:27 2019 +0200 | summary: "Hello Word!" is even nicer | o changeset: 4:29ce9b90e420 | user: paugier <pierre.augier@univ-grenoble-alpes.fr> ~ date: Fri Jun 14 23:20:25 2019 +0200 summary: Improve file2
We have 2 "unnamed branches" (in the "named branch" default), i.e. 2 different commits with the same parent (changeset 5).
A simple way to avoid this situation is to pull just before a commit and to make sure to commit from the last commit in the shared repository.
There are different ways to solve this problem. The nicest would be to move changeset 6 above changeset 7 (actually change the parent of changeset 6 to changeset 7). It's very simple to do with the command hg rebase -s 6 -d 7
(-s
and -d
mean source and destination, respectively). It is nice since it leads to a linear state but it is a little bit too complicated (history rewriting).
In the error log, Mercurial tells us about the simple way to solve the situation: pull and merge. Let's try that.
hg merge
1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit)
hg sum
parent: 6:f229009f4e26 Bob adds a file parent: 7:249808fd5597 tip Alice adds a file branch: default commit: 1 modified (merge) update: (current) phases: 1 draft
hg st
M Alice_file.py # The repository is in an unfinished *merge* state. # To continue: hg commit # To abort: hg merge --abort
ls
Alice_file.py Bob_file.py file1.txt file2.txt
hg commit -m "Merge Alice and Bob commits"
hg log -G -l 4
@ changeset: 8:87303dcc8d97 |\ tag: tip | | parent: 6:f229009f4e26 | | parent: 7:249808fd5597 | | user: Bob <bob@mail.wonderland> | | date: Fri Jun 14 23:20:33 2019 +0200 | | summary: Merge Alice and Bob commits | | | o changeset: 7:249808fd5597 | | parent: 5:f2242950a7e7 | | user: Alice <alice@mail.wonderland> | | date: Fri Jun 14 23:20:30 2019 +0200 | | summary: Alice adds a file | | o | changeset: 6:f229009f4e26 |/ user: Bob <bob@mail.wonderland> | date: Fri Jun 14 23:20:31 2019 +0200 | summary: Bob adds a file | o changeset: 5:f2242950a7e7 | user: paugier <pierre.augier@univ-grenoble-alpes.fr> ~ date: Fri Jun 14 23:20:27 2019 +0200 summary: "Hello Word!" is even nicer
Note that the history is no longer linear. However, the repository is now in a good state so we should be able to push in the remote repository.
hg push
pushing to /tmp/myrepos/myrepo_web searching for changes adding changesets adding manifests adding file changes added 2 changesets with 1 changes to 1 files
And Alice can get Bob's file.
# Alice works
cd /tmp/myrepos/myrepo_Alice
hg pull -u
pulling from /tmp/myrepos/myrepo_web searching for changes adding changesets adding manifests adding file changes added 2 changesets with 1 changes to 1 files new changesets f229009f4e26:87303dcc8d97 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
hg sum
parent: 8:87303dcc8d97 tip
Merge Alice and Bob commits
branch: default
commit: (clean)
update: (current)
ls
Alice_file.py Bob_file.py file1.txt file2.txt
https://gricad-gitlab.univ-grenoble-alpes.fr
Clone it with Mercurial locally
Note the difference between the https and ssh addresses. To use ssh, one first has to setup a ssh key.
Create a README.md and fill it with few lines of markdown code.
Commit and push:
hg st
hg add
hg commit -m "Modify README.md"
hg push
It's very useful to use a Makefile. Everything can be compiled just with the command make
. Complicated Makefile syntaxes are not mandatory!
The commands latexmk
and latexdiff
can be really useful!
Here is a simple example:
name := my_paper
LATEX := pdflatex -shell-escape -synctex=1
.PHONY: all clean cleanall
all: $(name).pdf
revision.tex:
hg cat -r 0.2.0 $(name).tex > $(name)_submitted.tex
latexdiff -c PICTUREENV=minted $(name)_submitted.tex $(name).tex > revision.tex
rm -f $(name)_submitted.tex
revision.pdf: revision.tex
latexmk -pdf -pdflatex="$(LATEX)" revision.tex
rm -f revision.tex
revision: revision.tex revision.pdf clean
rebuttal.pdf: rebuttal.md
pandoc $< -o $@
clean:
rm -f *.log *.aux *.out *.bbl *.blg *.tmp
rm -rf _minted-$(name)
cleanall: clean
rm -f $(name).pdf
$(name).pdf: $(name).tex
latexmk -pdf -pdflatex="$(LATEX)" $(name).tex