Introduction to source control management with Mercurial and Gitlab#

Motivations#

Why source control management?#

Source control management (also known as simply “Git” nowadays 🙂) is an essential tool for scientists and developers!

Why Mercurial?#

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.

git

I (Pierre Augier) think that Mercurial is more adapted for us in academics:

  • simpler and safer for beginners,

git
  • very powerful for advanced users (it is used for example at Facebook and Mozilla… and for the PyPy repository).

For advanced Git users, when using Mercurial, you lose 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).

Why Gitlab?#

  • GitLab is a web-based DevOps lifecycle tool that provides a Git-repository manager providing wiki, issue-tracking and continuous integration and deployment pipeline features, using an open-source license” (from https://en.wikipedia.org/wiki/GitLab).

  • Gitlab is an open-source and self-hosted alternative to Github (which is not open-source, not self-hosted and now owned by Microsoft).

  • Nice and popular projects like Debian and Gnome use Gitlab.

  • Many public institutions choose Gitlab for their hosting tool, in particular our university UGA has it own Gitlab instance: https://gricad-gitlab.univ-grenoble-alpes.fr.

Gitlab does not natively support Mercurial!

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.

However, there is now Heptapod, a friendly fork of Gitlab with support for Mercurial and Git.

Level 0: command help#

export HGRCPATH=""
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:

 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

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)
 grep          search for a pattern in specified 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
 purge         removes files not tracked by Mercurial
 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:

 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

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
 rust          Rust in Mercurial

Concepts:

 bundlespec    Bundle File Formats
 evolution     Safely rewriting history (EXPERIMENTAL)
 glossary      Glossary
 phases        Working with Phases
 subrepos      Subrepositories

Miscellaneous:

 deprecated    Deprecated Features
 internals     Technical implementation topics
 scripting     Using Mercurial from scripts and automation

(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)
export HGRCPATH=$PWD/hgrc4ipynb

Level 0: one local repository#

git

Commands init, status, add, remove, addremove, commit, log and summary#

# cleanup
rm -rf /tmp/myrepos

Let’s create a directory and a file in it.

mkdir -p /tmp/myrepos/myrepo0
cd /tmp/myrepos/myrepo0
touch file0.txt

Let’s tell Mercurial that we want to make a new repository from this directory.

hg init

Let’s ask Mercurial what is new in this repository.

hg status
? file0.txt
# equivalent command (shorter)
hg st
? file0.txt

We want to “Schedule files to be version controlled and added to the repository.”

hg add
adding file0.txt

A new status gives

hg st
A file0.txt

Let’s now create the first commit (or “changeset”). We can think about it as a snapshot of the code as it is now.

hg commit -m "First commit"

A new status returns nothing because there is nothing new from the last commit.

hg st

Let’s add a new file:

touch file1.txt
hg st
? file1.txt
rm -f file0.txt
hg st
! file0.txt
? file1.txt

One new file and one file removed from the working directory. We want to take a new snapshot of the code how it is now. addremove is very useful is that common situation.

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” as it is in the working directory.

hg commit -m "Hello world in file1.txt"
hg st

We used a lot status to get information about the state of the repository. Let’s discover 2 other very useful commands: sum (or summary) and log.

hg sum
parent: 2:8bfba1b25e7d 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:8bfba1b25e7d
tag:         tip
user:        Pierre Augier <pa@example.com>
date:        Tue Sep 06 21:44:41 2022 +0200
summary:     Hello world in file1.txt

changeset:   1:02ac2678cff1
user:        Pierre Augier <pa@example.com>
date:        Tue Sep 06 21:44:40 2022 +0200
summary:     Remove file0 and add file1

changeset:   0:f7dcc9188623
user:        Pierre Augier <pa@example.com>
date:        Tue Sep 06 21:44:38 2022 +0200
summary:     First commit
hg log --graph
@  changeset:   2:8bfba1b25e7d
|  tag:         tip
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:41 2022 +0200
|  summary:     Hello world in file1.txt
|
o  changeset:   1:02ac2678cff1
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:40 2022 +0200
|  summary:     Remove file0 and add file1
|
o  changeset:   0:f7dcc9188623
   user:        Pierre Augier <pa@example.com>
   date:        Tue Sep 06 21:44:38 2022 +0200
   summary:     First commit
hg log -G
@  changeset:   2:8bfba1b25e7d
|  tag:         tip
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:41 2022 +0200
|  summary:     Hello world in file1.txt
|
o  changeset:   1:02ac2678cff1
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:40 2022 +0200
|  summary:     Remove file0 and add file1
|
o  changeset:   0:f7dcc9188623
   user:        Pierre Augier <pa@example.com>
   date:        Tue Sep 06 21:44:38 2022 +0200
   summary:     First commit

Level 0.1: about the special files .hgignore and .gitignore#

There are files that you don’t want to include in a repository. Usually, you want to include the source but not what is produced with the source (programs, libraries, pdf files, etc.).

To avoid to have too many files listed when one call hg st, one can add a file .hgignore (or .gitignore, when using hg-git).

See https://www.mercurial-scm.org/wiki/.hgignore and https://git-scm.com/docs/gitignore

Typical content of a .gitignore file:

*.pyd*
*.pyc
*~
*tmp
\#*\#
*.slides.html
.ipynb_checkpoints

Level 1.0: command revert#

cat file1.txt
Hello world!
echo "Something wrong" > file1.txt

Oh, here, we really did something wrong. It’s buggy. Let’s see where is our problem.

hg st
M file1.txt

The command diff shows the differences between the working directory and the parent commit.

hg diff
diff --git a/file1.txt b/file1.txt
--- a/file1.txt
+++ b/file1.txt
@@ -1,1 +1,1 @@
-Hello world!
+Something wrong

We could also use hg meld which opens the program meld to present the same thing graphically.

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

Level 1.1: command update#

hg log -G
@  changeset:   2:8bfba1b25e7d
|  tag:         tip
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:41 2022 +0200
|  summary:     Hello world in file1.txt
|
o  changeset:   1:02ac2678cff1
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:40 2022 +0200
|  summary:     Remove file0 and add file1
|
o  changeset:   0:f7dcc9188623
   user:        Pierre Augier <pa@example.com>
   date:        Tue Sep 06 21:44:38 2022 +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:f7dcc9188623 
 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

We were at commit 0 and we commited. It created a (unnamed) branch.

hg log -G
@  changeset:   3:d7f0bba98ea8
|  tag:         tip
|  parent:      0:f7dcc9188623
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:46 2022 +0200
|  summary:     A line in file0.txt
|
| o  changeset:   2:8bfba1b25e7d
| |  user:        Pierre Augier <pa@example.com>
| |  date:        Tue Sep 06 21:44:41 2022 +0200
| |  summary:     Hello world in file1.txt
| |
| o  changeset:   1:02ac2678cff1
|/   user:        Pierre Augier <pa@example.com>
|    date:        Tue Sep 06 21:44:40 2022 +0200
|    summary:     Remove file0 and add file1
|
o  changeset:   0:f7dcc9188623
   user:        Pierre Augier <pa@example.com>
   date:        Tue Sep 06 21:44:38 2022 +0200
   summary:     First commit
hg up 1
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
hg sum
parent: 1:02ac2678cff1 
 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 "8bfba1b25e7d: Hello world in file1.txt"
1 other heads for branch "default"
hg sum
parent: 2:8bfba1b25e7d 
 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:bd539f0ab390
|  tag:         tip
|  parent:      2:8bfba1b25e7d
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:49 2022 +0200
|  summary:     Add file 2
|
| o  changeset:   3:d7f0bba98ea8
| |  parent:      0:f7dcc9188623
| |  user:        Pierre Augier <pa@example.com>
| |  date:        Tue Sep 06 21:44:46 2022 +0200
| |  summary:     A line in file0.txt
| |
o |  changeset:   2:8bfba1b25e7d
| |  user:        Pierre Augier <pa@example.com>
| |  date:        Tue Sep 06 21:44:41 2022 +0200
| |  summary:     Hello world in file1.txt
| |
o |  changeset:   1:02ac2678cff1
|/   user:        Pierre Augier <pa@example.com>
|    date:        Tue Sep 06 21:44:40 2022 +0200
|    summary:     Remove file0 and add file1
|
o  changeset:   0:f7dcc9188623
   user:        Pierre Augier <pa@example.com>
   date:        Tue Sep 06 21:44:38 2022 +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:bd539f0ab390
|  tag:         tip
|  parent:      2:8bfba1b25e7d
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:49 2022 +0200
|  summary:     Add file 2
|
o  changeset:   2:8bfba1b25e7d
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:41 2022 +0200
|  summary:     Hello world in file1.txt
|
o  changeset:   1:02ac2678cff1
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:40 2022 +0200
|  summary:     Remove file0 and add file1
|
o  changeset:   0:f7dcc9188623
   user:        Pierre Augier <pa@example.com>
   date:        Tue Sep 06 21:44:38 2022 +0200
   summary:     First commit

Level 2: working with a remote repository (usually on the web)#

git

Commands clone, push, pull#

The most standard way to create a local repository is to clone a remote repository available on the web. This is done with the command clone like this:

hg clone ssh://hg@foss.heptapod.net/fluiddyn/fluidsim

Here, I used ssh, which is usually more convenient than https, but one can also use http addresses like https://github.com/hpyproject/hpy.git. On Github, Gitlab, Heptapod, there is usually a blue button Code or Clone from which one can get the right address to clone a repository.

Warning: sometimes, the addresses are not exactly like the web address, for example git@github.com:hpyproject/hpy.git.

Note: with the extension hggit, one can work on remote Git repositories with a local Mercurial repository (more on that later).

For this tutorial, we will create a local “web” repository.

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 f7dcc9188623:bd539f0ab390
updating to branch default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved

Now, we have a “web” repository that we can use as if it was on the web. Let’s first clone it.

hg clone myrepo_web myrepo1
updating to branch default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
cd myrepo1
ls
file1.txt  file2.txt
echo "Hello" > file2.txt
hg st
M file2.txt
hg commit -m "Improve file2"
hg log -G
@  changeset:   4:c3c17f042f45
|  tag:         tip
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:51 2022 +0200
|  summary:     Improve file2
|
o  changeset:   3:bd539f0ab390
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:49 2022 +0200
|  summary:     Add file 2
|
o  changeset:   2:8bfba1b25e7d
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:41 2022 +0200
|  summary:     Hello world in file1.txt
|
o  changeset:   1:02ac2678cff1
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:40 2022 +0200
|  summary:     Remove file0 and add file1
|
o  changeset:   0:f7dcc9188623
   user:        Pierre Augier <pa@example.com>
   date:        Tue Sep 06 21:44:38 2022 +0200
   summary:     First commit
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

At this point, we have 2 repositories.

cd /tmp/myrepos
pwd
ls
/tmp/myrepos
myrepo0  myrepo1  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 ../myrepo1
cat file2.txt
Hello
hg sum
parent: 4:c3c17f042f45 tip
 Improve file2
branch: default
commit: (clean)
update: (current)
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
new changesets d416bb8bde96
(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: 5:d416bb8bde96 tip
 "Hello Word!" is even nicer
branch: default
commit: (clean)
update: (current)
cat file2.txt
Hello Word!

Level 3: nonlinear history, unnamed branches and merge/rebase commands#

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 faaf587b5bd9
(run 'hg heads' to see heads, 'hg merge' to merge)

Now we can examine the situation.

hg heads
changeset:   7:faaf587b5bd9
tag:         tip
parent:      5:d416bb8bde96
user:        Alice <alice@mail.wonderland>
date:        Tue Sep 06 21:44:57 2022 +0200
summary:     Alice adds a file

changeset:   6:39d0c9e5a69f
user:        Bob <bob@mail.wonderland>
date:        Tue Sep 06 21:44:58 2022 +0200
summary:     Bob adds a file
hg log -G -l 4
o  changeset:   7:faaf587b5bd9
|  tag:         tip
|  parent:      5:d416bb8bde96
|  user:        Alice <alice@mail.wonderland>
|  date:        Tue Sep 06 21:44:57 2022 +0200
|  summary:     Alice adds a file
|
| @  changeset:   6:39d0c9e5a69f
|/   user:        Bob <bob@mail.wonderland>
|    date:        Tue Sep 06 21:44:58 2022 +0200
|    summary:     Bob adds a file
|
o  changeset:   5:d416bb8bde96
|  user:        Pierre Augier <pa@example.com>
|  date:        Tue Sep 06 21:44:53 2022 +0200
|  summary:     "Hello Word!" is even nicer
|
o  changeset:   4:c3c17f042f45
|  user:        Pierre Augier <pa@example.com>
~  date:        Tue Sep 06 21:44:51 2022 +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:39d0c9e5a69f 
 Bob adds a file
parent: 7:faaf587b5bd9 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:ba60c7f77030
|\   tag:         tip
| |  parent:      6:39d0c9e5a69f
| |  parent:      7:faaf587b5bd9
| |  user:        Bob <bob@mail.wonderland>
| |  date:        Tue Sep 06 21:45:00 2022 +0200
| |  summary:     Merge Alice and Bob commits
| |
| o  changeset:   7:faaf587b5bd9
| |  parent:      5:d416bb8bde96
| |  user:        Alice <alice@mail.wonderland>
| |  date:        Tue Sep 06 21:44:57 2022 +0200
| |  summary:     Alice adds a file
| |
o |  changeset:   6:39d0c9e5a69f
|/   user:        Bob <bob@mail.wonderland>
|    date:        Tue Sep 06 21:44:58 2022 +0200
|    summary:     Bob adds a file
|
o  changeset:   5:d416bb8bde96
|  user:        Pierre Augier <pa@example.com>
~  date:        Tue Sep 06 21:44:53 2022 +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 39d0c9e5a69f:ba60c7f77030
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
hg sum
parent: 8:ba60c7f77030 tip
 Merge Alice and Bob commits
branch: default
commit: (clean)
update: (current)
ls
Alice_file.py  Bob_file.py  file1.txt  file2.txt

Working with Gitlab#

We are going to use the UGA instance https://gricad-gitlab.univ-grenoble-alpes.fr.

  • Login on the website.

  • From the website, create a repository

  • Clone it with Mercurial locally (once again, you need to activate the extension hggit enabled in the user Mercurial config file).

    Note the difference between the https and ssh addresses. To use ssh, one first has to setup a ssh key. Let’s do it! It is easy and very useful!

  • Create a README.md file and fill it with few lines of markdown code.

  • Commit and push:

hg st
hg add
# create a commit locally on your computer
hg commit -m "Modify README.md"
# push it on the web server
hg push

Mercurial configuration files#

Simple text files… They can be edited with any text editors.

  • ~/.hgrc (per user)

If you don’t have this file, you can create a reasonable conf. file with hg config --edit. See https://fluiddyn.readthedocs.io/en/latest/mercurial_heptapod.html#set-up-mercurial. This file contains in particular the list of activated Mercurial extensions.

To use Mercurial with Git repositories hosted on Github/Gitlab, one need at least (yes, nothing after the =):

[extensions]
hggit =
  • my_repo/.hg/hgrc (per repo)

This file contains in particular the path from which to pull/push by default. Something like:

[paths]
default = ssh://hg@foss.heptapod.net/fluiddyn/transonic

To work with hg-git, it is convenient to have something like:

[paths]
default = git@github.com:paugier/pythran.git
upstream = https://github.com/serge-sans-paille/pythran.git

Standard Github / Gitlab workflow#

git

Git branches are really like “bookmarks” that point towards a commit. In Mercurial, such things are called “bookmarks”.

To create a Git branch in your remote forked repository, one can do:

hg pull upstream
# update the code to the last commit of the `main` Git branch
hg up main
# create a new bookmark for our new Git branch
hg bookmark my-fix-or-new-feature
hg commit
# ...
hg push -B my-fix-or-new-feature

Then, one can create the Pull Request (Github) or Merge Request (Gitlab). Usually, it it done through the web interface.

Heptapod workflow (without forks)#

git

Mercurial feature branches are called “topics”.

To create a Merge Request in the remote repository, one can do:

hg pull
hg up default
hg topic my-fix-or-new-feature
hg commit
# ...
hg push