Making mercurial subrepositories behave like subversion externals - mercurial

The FAQ, and hginit.com have been really useful for helping me make the transition from svn to hg.
However, when it comes to using Hg's subrepository feature in the manner of subversion's externals, I've tried everythign and cannot replicate the nice behavior of svn externals.
Here's the simplest example of what I want to do:
Init "lib" repository
This repository is never to be used as a standalone; it's always included by main
repositories, as a sub-repository.
Init one or more including repositories
To keep the example simple, I'll "init" a repository called "main"
Have "main" include "lib" as a subrepository
Importantly -- AND HERE'S WHAT I CAN'T GET TO WORK:
When I modify a file inside of "main/lib", and I push the modification,
then that change gets pushed to the "lib" repository -- NOT to a copy
inside of "main".
Command lines speak louder than words. I've tried so many variations on this theme, but here's the gist. If someone can reply, in command lines, I'll be forever grateful!
1. Init "lib" repository
$ cd /home/moi/hgrepos ## Where I'm storing my hg repositories, on my main server
$ hg init lib
$ echo "foo" > lib/lib.txt
$ hg add lib
$ hg ci -A -m "Init lib" lib
2. Init "main" repository, and include "lib" as a subrepos
$ cd /home/moi/hgrepos
$ hg init main
$ echo "foo" > main/main.txt
$ hg add main
$ cd main
$ hg clone ../lib lib
$ echo "lib=lib" > .hgsub
$ hg ci -A -m "Init main" .
This all works fine, but when I make a clone of the "main" repository, and make local
modifications to files in "main/lib", and push them, the changes get pushed to "main/lib",
NOT to "lib".
IN COMMAND-LINE-ESE, THIS IS THE PROBLEM:
$ /home/moi/hg-test
$ hg clone ssh://moi#www.moi.com/hgrepos/lib lib
$ hg clone ssh://moi#www.moi.com/hgrepos/main main
$ cd main
$ echo foo >> lib/lib.txt
$ hg st
M lib.txt
$ hg com -m "Modified lib.txt, from inside the main repos" lib.txt
$ hg push
pushing to ssh://moi#www.moi.com/hgrepos/main/lib
That last line of output from hg shows the problem.
It shows that I've made a modification to a COPY of a file in lib, NOT to a file in the lib repository. If this were working as I'd like it to work, the push would be to hgrepos/lib, NOT to hgrepos/main/lib. I.e., I would see:
$ hg push
pushing to ssh://moi#www.moi.com/hgrepos/lib
IF YOU CAN ANSWER THIS IN TERMS
OF COMMAND LINES RATHER THAN IN ENGLISH,
I WILL BE ETERNALLY GRATEFUL!
Thank you in advance!
Emily in Portland

The problem is with your .hgsub file. It points to where the lib repo is, so if lib is a sibling to main it should be:
lib=../lib
Also your hg add lib and hg add main lines don't make sense. To what repo outside of main and lib are those being added? You're running them while in in /home/moi/hgrepos.
Here's your script with some tweaks:
+ cd /home/ry4an/hgtest
+ hg init lib
+ echo foo
+ cd lib
+ hg commit -A -m Init lib
adding lib.txt
+ cd /home/ry4an/hgtest
+ hg init main
+ echo foo
+ cd main
+ echo lib=../lib
+ hg clone ../lib
destination directory: lib
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ hg add .hgsub main.txt
+ hg commit -m Init main: initial file and a .hgsub
committing subrepository lib
+ cd /home/ry4an/hgtest
+ hg clone main main-clone
updating to branch default
pulling subrepo lib
requesting all changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ cd main-clone
+ echo foo
+ hg commit -m Modified lib.txt, from inside the main repos
committing subrepository lib
+ hg push
pushing to /home/ry4an/hgtest/main
pushing subrepo lib
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
To do that over ssh:// you need only make a single change. When cloning the main repo change hg clone main main-clone to hg clone ssh://host/hgtest/main main-clone -- cloning the main automatically clones the lib -- that's the subrepo benefit.
Here's a log of that working:
+ cd /home/ry4an/hgtest
+ hg init lib
+ echo foo
+ cd lib
+ hg commit -A -m Init lib
adding lib.txt
+ cd /home/ry4an/hgtest
+ hg init main
+ echo foo
+ cd main
+ echo lib=../lib
+ hg clone ../lib
destination directory: lib
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ hg add .hgsub main.txt
+ hg commit -m Init main: initial file and a .hgsub
committing subrepository lib
+ cd /home/ry4an/hgtest
+ hg clone ssh://localhost/hgtest/main main-clone
The authenticity of host 'localhost (::1)' can't be established.
RSA key fingerprint is 0c:58:d6:d3:d3:16:14:ee:3b:be:01:bc:c7:3c:92:0b.
Are you sure you want to continue connecting (yes/no)? yes
ry4an#localhost's password:
requesting all changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 3 changes to 3 files
updating to branch default
pulling subrepo lib
ry4an#localhost's password:
requesting all changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
3 files updated, 0 files merged, 0 files removed, 0 files unresolved
remote: Warning: Permanently added 'localhost' (RSA) to the list of known hosts.
+ cd main-clone
+ echo foo
+ hg commit -m Modified lib.txt, from inside the main repos
committing subrepository lib
+ hg push
ry4an#localhost's password:
pushing to ssh://localhost/hgtest/main
pushing subrepo lib
ry4an#localhost's password:
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files

Oops, sorry for the formatting in previous answer. Here it is again, formatted!
So, here are the two scenarios most folks will be faced with:
A) Using subrepositories in a completely local situation.
This is Ryan's solution, essentially.
I imagine only developers working solo will be in this boat.
cd /home/moi/hgrepos
hg init lib
cd lib
echo foo > lib.txt
hg ci -A -m Init
cd /home/moi/hgrepos
hg init main
cd main
echo foo > main.txt
echo lib = ../lib > .hgsub
hg clone ../lib
hg add .hgsub main.txt
hg ci -m Init
cd /home/moi/hgrepos
hg clone main main-clone
cd main-clone/lib
echo "Modified while on main trunk" >>lib.txt
hg commit -m "Modified lib.txt, while on main trunk"
hg push
cd /home/moi/hgrepos/lib
hg update
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
cat lib.txt
foo
Modified while on main trunk
-----------------------------------------------------------------
B) Using subrepositories over ssh.
I imagine most developers working on teams will be in this boat.
1) Set up lib
cd /home/moi/hgrepos
hg init lib
cd lib
echo foo > lib.txt
hg ci -A -m Init
2) Set up main
cd /home/moi/hgrepos
hg init main
cd main
echo foo > main.txt
echo lib=ssh://moi#www.moi.com/hgrepos/lib > .hgsub
hg clone ssh://moi#www.moi.com/hgrepos/lib lib
hg add .hgsub main.txt
hg ci -m Init
3) Clone lib to hgtest dir
cd /home/moi/hgtest
hg clone ssh://moi#www.moi.com/hgrepos/lib lib
4) Clone main to hgtest dir
cd /home/moi/hgtest
hg clone ssh://moi#www.moi.com/hgrepos/main main
5) Modify lib.txt while on main trunk
cd /home/moi/hgtest/main/lib
echo "Modified while on main trunk" >>lib.txt
hg commit -m "Modified lib.txt, while on main trunk"
hg push
6) Verify that lib.txt got changed in the lib repository
cd /home/moi/hgtest/lib
hg pull
hg update
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
cat lib.txt
foo
Modified while on main trunk

Related

Cant create a clone in Mercurial

i just installed mercurial and tortoise hg and Im a newbie.
I write this commeand at cmd to create a clone: myfile.txt hg clone. But then I cant seee any clone created.And myfile is opened. Can someone help me? Where can I find this created clone?Thanks!
Like Nanhydrin writes: hg clone is to clone repositories.
What is a repository? It is a project folder where you have run hg init. Mercurial. You get a repository by taking a project folder and running hg init. Then you can add any files in that folder or subfolders and use hg add to add them to version control.
Turn a project directory into a repository:
cd my-project
hg init # start a repository in the current directory
# tell Mercurial to start tracking these files
hg add file1 file2 file3 ...
# commit the files you just added. This is the first commit!
hg commit
Clone a repository:
hg clone C:/my-mercurial-repos/my-project
[creates a clone of the my-project repository in my-repo]
cd my-project # Enter the repository
You can't 'clone a file', but you can create a copy that Mercurial will also track.
hg copy my-file.txt my-file-copy.txt
hg commit
For the rest, take Nanhydrin's excellent advice and read http://hginit.com/index.html. It helped me a lot when I started using Mercurial.

Mercurial: roll back an "hg commit --amend".

I accidentally did a "hg commit --amend" instead of just a commit. How can I roll back the commit to before the amend?
You can use hg reflog (from the journal extension) and hg reset <hash>.
hg reflog -v
should give something like:
<old-hash> -> <new-hash> <user> <timestamp> commit --amend <some-path>
if that is the amend you want to revert, just use:
hg reset <old-hash>
The commit will be reverted to what is previously was and the changes that were amended should now be uncommitted changes (check using hg status and hg diff).
If your version of Mercurial is new enough, I believe you should be able to use the hg unamend command from the uncommit extension that ships with Mercurial. This may require that obsolescence markers are enabled, I'm not sure.
Enable the uncommit extension, add this to your ~/.hgrc:
[extensions]
uncommit =
Actually run the unamend:
hg unamend
Find the latest saved backup in .hg/strip-backup directory
hg unbundle .hg/strip-backup/<latest backup>
Now you should have two heads - one with the amended commit, other one with two commits (first one - old commit before amending, second one caled: "temporary amend commit for (old commit hash)".
if you have histedit extension, you can do hg histedit on it in order to change it (e.g. select edit in order to achieve a state just before the commit, i.e. when you can see all changes using hg diff).
Don't forget to strip the old head.
It's 2022, and my attempts to use hg unamend have not worked. histedit is too clunky for my purposes, but the solution proposed by mariu52 elsewhere on this page can easily be adapted to work without histedit. It relies on the -k option of the strip subcommand.
In a nutshell:
Find the latest saved backup in the .hg/strip-backup/ directory
Run hg unbundle .hg/strip-backup/<latest backup> where <latest backup> signifies the full filename.
Run hg heads and note the rev number corresponding to the amendment.
Let's call this $AMENDREV; this is the rev number we will strip in the next step.
Run hg strip -k --rev $AMENDREV
Using the -k option in the strip command is critical.
WARNING: this procedure will in effect erase the memory of any add or remove commands
that were pending when the amend command was executed.
For example, in the transcript below, the hg add file2 command is effectively
forgotten after the strip command is executed.
For clarity, here's a transcript based on the above recipe.
$ mkdir tmp ; cd tmp
$ ls
$ echo 1 > file1
$ echo 2 > file2
$ hg init
$ ls
file1 file2
$ hg add file1
$ hg commit -m 'one file'
$ hg add file2
$ hg amend -m 'amendment'
saved backup bundle to /tmp/tmp/.hg/strip-backup/d332ee829c21-5a5f23b0-amend.hg
$ hg unbundle -u .hg/strip-backup/d332ee829c21-5a5f23b0-amend.hg
adding changesets
adding manifests
adding file changes
added 1 changesets with 0 changes to 1 files (+1 heads)
new changesets d332ee829c21 (1 drafts)
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
updated to "1a445f3252eb: amendment"
1 other heads for branch "default"
$ hg heads
1[tip]:-1 d332ee829c21 2022-11-09 01:55 -0500 peak
one file
0 1a445f3252eb 2022-11-09 01:55 -0500 peak
amendment
$ ls
file1 file2
$ hg strip -k -r 0
saved backup bundle to /tmp/tmp/.hg/strip-backup/1a445f3252eb-bfaab5ec-backup.hg
$ ls
file1 file2
$ hg list
r0: peak tip 2022-11-09 01:55 -0500
one file
file1
$
hg unamend part of Mercurial 4.5 (2018-02-01).
NOTE: This answer is now deprecated. See the answer from #Sorina Sandu instead.
See hg help commit, where it says:
The --amend flag can be used to amend the parent of the working
directory with a new commit that contains the changes in the parent in
addition to those currently reported by "hg status", if there are any.
The old commit is stored in a backup bundle in ".hg/strip-backup" (see
"hg help bundle" and "hg help unbundle" on how to restore it).

how do you define which changeset a kiln/mercurial subrepository uses?

In the answer to this question on the kiln stack exchange site, there is a comment that mentions "if you commit from one consumer of the library, the other library consumers do not immediately see those changesets. You have to explicitly pull the changes on the library repo in other consumers."
i have added a few files to a repository which is referred to in a projects .hgsub & .hgsubstate files, but they are not showing up in the projects subrespository (because the project is quite rightly using the previous change-set it was earlier assigned)
I'd like to know how to edit which changeset a subrepo uses. do I just edit the .hgsubstate file (seems a little "hackish") or is there a command / kiln website option I can use?
In the subrepository, hg update to the changeset you want the main repository to use. Then, in the main repository, issue hg ci to commit the subrepository change. Mercurial will automatically update the .hgsubstate file with the current parent changeset ID of the subrepository.
Example (Windows .bat file):
REM Create main repository
hg init Example
cd Example
echo >file1
hg ci -Am file1
cd ..
REM Create another repository
hg init Library
cd Library
echo >file2
hg ci -Am file2
cd ..
REM Clone the Library into the main repository
cd Example
hg clone ..\Library
REM and configure it as a subrepository.
echo Library=Library >.hgsub
REM Commit it.
hg ci -Am "Added Library sub repository."
REM Note .hgsubstate is updated with the current subrepo changeset.
type .hgsubstate
cd ..
REM Someone updates the original Library.
cd Library
echo >file3
hg ci -Am file3
cd ..
REM Main repo isn't affected. It has a clone of Library.
cd Example
hg status -S
REM Update to the latest library
cd Library
hg pull -u
cd ..
REM View the changes to the library in the main repo.
hg status -S
REM Commit the library update in the main repo.
hg ci -m "Updated library."
REM Note .hgsubstate is updated.
type .hgsubstate

How is the SCM Command generated by Jenkins

I have mercurial repository, and by providing the URL to Jenkins, i get the following in stdout:
Started by an SCM change
[workspace] $ hg incoming --quiet --bundle hg.bundle --template "{desc|xmlescape}{file_adds|stringify|xmlescape}{file_dels|stringify|xmlescape}{files|stringify|xmlescape}{parents}\n" --rev default
[workspace] $ hg unbundle hg.bundle
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
(run 'hg update' to get a working copy)
[workspace] $ hg update --clean --rev default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
[workspace] $ hg log --rev . --template {node}
No emails were triggered.
Question is HOW are these commands generated and can they be modified ?
Those commands are part of the Mercurial Jenkins plugin, with the source code available at GitHub.
You can find the incoming command used in the hudson.plugins.mercurial.MercurialSCM.java class in the update() method.
os.write("<changesets>\n".getBytes());
ArgumentListBuilder args = findHgExe(build, listener, false);
args.add(forest ? "fincoming" : "incoming", "--quiet");
if (!forest) {
args.add("--bundle", "hg.bundle");
}
args.add("--template", MercurialChangeSet.CHANGELOG_TEMPLATE);
args.add("--rev", getBranch(env));

Mercurial Subrepos, how to control which changeset I want to use for a subrepo?

I am reading up on subrepos, and have been running some tests locally, seems to work OK so far, but I have one question.
How do I specify/control which changeset I want to use for a particular subrepo?
For instance, let's say I have the following two projects:
class library application
o fourth commit o second commit, added a feature
| |
o third commit o initial commit
|
| o second commit
|/
o initial commit
Now, I want the class library as a subrepo of my application, but due to the immaturity of the longest branch (the one ending up as fourth commit), I want to temporarily use the "second commit" tip.
How do I go about configuring that, assuming it is even possible?
Here's a batch file that sets up the above two repos + adds the library as a subrepo.
If you run the batch file, it will output:
[C:\Temp] :test
...
v4
As you can see from that last line there, it verifies the contents of the file in the class library, which is "v4" from the fourth commit. I'd like it to be "v2", and persist as "v2" until I'm ready to pull down a newer version from the class library repository.
Can anyone tell me if it is possible to do what I want, and if so, what I need to do in order to lock my subrepo to the right changeset?
Batch-file:
#echo off
if exist app rd /s /q app
if exist lib rd /s /q lib
if exist app-clone rd /s /q app-clone
rem == app ==
hg init app
cd app
echo program>main.txt
hg add main.txt
hg commit -m "initial commit"
echo program+feature1>main.txt
hg commit -m "second commit, added a feature"
cd ..
rem == lib ==
hg init lib
cd lib
echo v1>lib.txt
hg add lib.txt
hg commit -m "initial commit"
echo v2>lib.txt
hg commit -m "second commit"
hg update 0
echo v3>lib.txt
hg commit -m "third commit"
echo v4>lib.txt
hg commit -m "fourth commit"
cd ..
rem == subrepos ==
cd app
hg clone ..\lib lib
echo lib = ..\lib >.hgsub
hg add .hgsub
hg commit -m "added subrepo"
cd ..
rem == clone ==
hg clone app app-clone
type app-clone\lib\lib.txt
Edit: Ok, I got my answer, thanks #VonC, I added the following section to my batch-file, above the rem == clone == line, and re-executed it, and now it locks the subrepo to the correct changeset.
rem == lock ==
cd app\lib
hg update 1
cd ..
hg commit -m "lock to second commit"
cd ..
Not tested, but you should be able to go within your subrepo, update its content to the right commit (hg update), go back up one level (in the main project) and commit.
That should update the .hgsubstate with the right commit.
(extreme workaround, update that .hgsubstate yourself, but that is not recommended.)
The all idea of hg subrepos (or Git submodules) is to allow a dependency management by referencing a fixed id for a given sub-repo. If no id is given when creating the subrepo, then the latest id is selected (v4 in your case), but you can checkout whatever id you need.
Actually, this thread even complains that:
Right now, commit recursively tries to commit subrepositories before committing the current repository.
That allows you to:
record some changes in the sub-repo.
update the .hgsubstate of the main project with the new state (id) of the subrepo.
Your sub-repo revision won't be advanced without you explicitly choosing to do so, so all you have to do is set it up as you'd like initially. When initially creating the sub repo, just use a '-r' argument to clone only up to the changeset you'd like:
rem == subrepos ==
cd app
hg clone -r CHANGESETYOUWANT ..\lib lib
echo lib = ..\lib >.hgsub
hg add .hgsub
hg commit -m "added subrepo"
cd ..
note the modification on line three.