557 |
557 |
558 # If this commit was the result of `hg fold` after submission, |
558 # If this commit was the result of `hg fold` after submission, |
559 # and now resubmitted with --fold, the easiest thing to do is |
559 # and now resubmitted with --fold, the easiest thing to do is |
560 # to leave the node clear. This only results in creating a new |
560 # to leave the node clear. This only results in creating a new |
561 # diff for the _same_ Differential Revision if this commit is |
561 # diff for the _same_ Differential Revision if this commit is |
562 # the first or last in the selected range. |
562 # the first or last in the selected range. If we picked a node |
|
563 # from the list instead, it would have to be the lowest if at |
|
564 # the beginning of the --fold range, or the highest at the end. |
|
565 # Otherwise, one or more of the nodes wouldn't be considered in |
|
566 # the diff, and the Differential wouldn't be properly updated. |
563 # If this commit is the result of `hg split` in the same |
567 # If this commit is the result of `hg split` in the same |
564 # scenario, there is a single oldnode here (and multiple |
568 # scenario, there is a single oldnode here (and multiple |
565 # newnodes mapped to it). That makes it the same as the normal |
569 # newnodes mapped to it). That makes it the same as the normal |
566 # case, as the edges of the newnode range cleanly maps to one |
570 # case, as the edges of the newnode range cleanly maps to one |
567 # oldnode each. |
571 # oldnode each. |
1288 |
1299 |
1289 if not revs: |
1300 if not revs: |
1290 raise error.Abort(_(b'phabsend requires at least one changeset')) |
1301 raise error.Abort(_(b'phabsend requires at least one changeset')) |
1291 if opts.get(b'amend'): |
1302 if opts.get(b'amend'): |
1292 cmdutil.checkunfinished(repo) |
1303 cmdutil.checkunfinished(repo) |
|
1304 |
|
1305 ctxs = [repo[rev] for rev in revs] |
|
1306 |
|
1307 fold = opts.get(b'fold') |
|
1308 if fold: |
|
1309 if len(revs) == 1: |
|
1310 # TODO: just switch to --no-fold instead? |
|
1311 raise error.Abort(_(b"cannot fold a single revision")) |
|
1312 |
|
1313 # There's no clear way to manage multiple commits with a Dxxx tag, so |
|
1314 # require the amend option. (We could append "_nnn", but then it |
|
1315 # becomes jumbled if earlier commits are added to an update.) It should |
|
1316 # lock the repo and ensure that the range is editable, but that would |
|
1317 # make the code pretty convoluted. The default behavior of `arc` is to |
|
1318 # create a new review anyway. |
|
1319 if not opts.get(b"amend"): |
|
1320 raise error.Abort(_(b"cannot fold with --no-amend")) |
|
1321 |
|
1322 # Ensure the local commits are an unbroken range |
|
1323 revrange = repo.revs(b'(first(%ld)::last(%ld))', revs, revs) |
|
1324 if any(r for r in revs if r not in revrange) or any( |
|
1325 r for r in revrange if r not in revs |
|
1326 ): |
|
1327 raise error.Abort(_(b"cannot fold non-linear revisions")) |
|
1328 |
|
1329 # It might be possible to bucketize the revisions by the DREV value, and |
|
1330 # iterate over those groups when posting, and then again when amending. |
|
1331 # But for simplicity, require all selected revisions to be for the same |
|
1332 # DREV (if present). Adding local revisions to an existing DREV is |
|
1333 # acceptable. |
|
1334 drevmatchers = [ |
|
1335 _differentialrevisiondescre.search(ctx.description()) |
|
1336 for ctx in ctxs |
|
1337 ] |
|
1338 if len({m.group('url') for m in drevmatchers if m}) > 1: |
|
1339 raise error.Abort( |
|
1340 _(b"cannot fold revisions with different DREV values") |
|
1341 ) |
1293 |
1342 |
1294 # {newnode: (oldnode, olddiff, olddrev} |
1343 # {newnode: (oldnode, olddiff, olddrev} |
1295 oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs]) |
1344 oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs]) |
1296 |
1345 |
1297 confirm = ui.configbool(b'phabsend', b'confirm') |
1346 confirm = ui.configbool(b'phabsend', b'confirm') |
1321 diffmap = {} # {newnode: diff} |
1370 diffmap = {} # {newnode: diff} |
1322 |
1371 |
1323 # Send patches one by one so we know their Differential Revision PHIDs and |
1372 # Send patches one by one so we know their Differential Revision PHIDs and |
1324 # can provide dependency relationship |
1373 # can provide dependency relationship |
1325 lastrevphid = None |
1374 lastrevphid = None |
1326 for rev in revs: |
1375 for ctx in ctxs: |
1327 ui.debug(b'sending rev %d\n' % rev) |
1376 if fold: |
1328 ctx = repo[rev] |
1377 ui.debug(b'sending rev %d::%d\n' % (ctx.rev(), ctxs[-1].rev())) |
|
1378 else: |
|
1379 ui.debug(b'sending rev %d\n' % ctx.rev()) |
1329 |
1380 |
1330 # Get Differential Revision ID |
1381 # Get Differential Revision ID |
1331 oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None)) |
1382 oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None)) |
1332 oldbasenode = oldnode |
1383 oldbasenode, oldbasediff, oldbaserevid = oldnode, olddiff, revid |
|
1384 |
|
1385 if fold: |
|
1386 oldbasenode, oldbasediff, oldbaserevid = oldmap.get( |
|
1387 ctxs[-1].node(), (None, None, None) |
|
1388 ) |
|
1389 |
1333 if oldnode != ctx.node() or opts.get(b'amend'): |
1390 if oldnode != ctx.node() or opts.get(b'amend'): |
1334 # Create or update Differential Revision |
1391 # Create or update Differential Revision |
1335 revision, diff = createdifferentialrevision( |
1392 revision, diff = createdifferentialrevision( |
1336 [ctx], |
1393 ctxs if fold else [ctx], |
1337 revid, |
1394 revid, |
1338 lastrevphid, |
1395 lastrevphid, |
1339 oldbasenode, |
1396 oldbasenode, |
1340 oldnode, |
1397 oldnode, |
1341 olddiff, |
1398 olddiff, |
1342 actions, |
1399 actions, |
1343 opts.get(b'comment'), |
1400 opts.get(b'comment'), |
1344 ) |
1401 ) |
1345 diffmap[ctx.node()] = diff |
1402 |
|
1403 if fold: |
|
1404 for ctx in ctxs: |
|
1405 diffmap[ctx.node()] = diff |
|
1406 else: |
|
1407 diffmap[ctx.node()] = diff |
|
1408 |
1346 newrevid = int(revision[b'object'][b'id']) |
1409 newrevid = int(revision[b'object'][b'id']) |
1347 newrevphid = revision[b'object'][b'phid'] |
1410 newrevphid = revision[b'object'][b'phid'] |
1348 if revid: |
1411 if revid: |
1349 action = b'updated' |
1412 action = b'updated' |
1350 else: |
1413 else: |
1351 action = b'created' |
1414 action = b'created' |
1352 |
1415 |
1353 # Create a local tag to note the association, if commit message |
1416 # Create a local tag to note the association, if commit message |
1354 # does not have it already |
1417 # does not have it already |
1355 m = _differentialrevisiondescre.search(ctx.description()) |
1418 if not fold: |
1356 if not m or int(m.group('id')) != newrevid: |
1419 m = _differentialrevisiondescre.search(ctx.description()) |
1357 tagname = b'D%d' % newrevid |
1420 if not m or int(m.group('id')) != newrevid: |
1358 tags.tag( |
1421 tagname = b'D%d' % newrevid |
1359 repo, |
1422 tags.tag( |
1360 tagname, |
1423 repo, |
1361 ctx.node(), |
1424 tagname, |
1362 message=None, |
1425 ctx.node(), |
1363 user=None, |
1426 message=None, |
1364 date=None, |
1427 user=None, |
1365 local=True, |
1428 date=None, |
1366 ) |
1429 local=True, |
|
1430 ) |
1367 else: |
1431 else: |
1368 # Nothing changed. But still set "newrevphid" so the next revision |
1432 # Nothing changed. But still set "newrevphid" so the next revision |
1369 # could depend on this one and "newrevid" for the summary line. |
1433 # could depend on this one and "newrevid" for the summary line. |
1370 newrevphid = querydrev(repo.ui, b'%d' % revid)[0][b'phid'] |
1434 newrevphid = querydrev(repo.ui, b'%d' % revid)[0][b'phid'] |
1371 newrevid = revid |
1435 newrevid = revid |
1372 action = b'skipped' |
1436 action = b'skipped' |
1373 |
1437 |
1374 drevids.append(newrevid) |
1438 drevids.append(newrevid) |
1375 lastrevphid = newrevphid |
1439 lastrevphid = newrevphid |
|
1440 |
|
1441 if fold: |
|
1442 for c in ctxs: |
|
1443 if oldmap.get(c.node(), (None, None, None))[2]: |
|
1444 action = b'updated' |
|
1445 else: |
|
1446 action = b'created' |
|
1447 _print_phabsend_action(ui, c, newrevid, action) |
|
1448 break |
1376 |
1449 |
1377 _print_phabsend_action(ui, ctx, newrevid, action) |
1450 _print_phabsend_action(ui, ctx, newrevid, action) |
1378 |
1451 |
1379 # Update commit messages and remove tags |
1452 # Update commit messages and remove tags |
1380 if opts.get(b'amend'): |
1453 if opts.get(b'amend'): |
1381 unfi = repo.unfiltered() |
1454 unfi = repo.unfiltered() |
1382 drevs = callconduit(ui, b'differential.query', {b'ids': drevids}) |
1455 drevs = callconduit(ui, b'differential.query', {b'ids': drevids}) |
1383 with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'): |
1456 with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'): |
1384 wnode = unfi[b'.'].node() |
1457 wnode = unfi[b'.'].node() |
1385 mapping = {} # {oldnode: [newnode]} |
1458 mapping = {} # {oldnode: [newnode]} |
|
1459 newnodes = [] |
|
1460 |
|
1461 drevid = drevids[0] |
|
1462 |
1386 for i, rev in enumerate(revs): |
1463 for i, rev in enumerate(revs): |
1387 old = unfi[rev] |
1464 old = unfi[rev] |
1388 drevid = drevids[i] |
1465 if not fold: |
|
1466 drevid = drevids[i] |
1389 drev = [d for d in drevs if int(d[b'id']) == drevid][0] |
1467 drev = [d for d in drevs if int(d[b'id']) == drevid][0] |
1390 newdesc = get_amended_desc(drev, old, False) |
1468 |
|
1469 newdesc = get_amended_desc(drev, old, fold) |
1391 # Make sure commit message contain "Differential Revision" |
1470 # Make sure commit message contain "Differential Revision" |
1392 if old.description() != newdesc: |
1471 if old.description() != newdesc: |
1393 if old.phase() == phases.public: |
1472 if old.phase() == phases.public: |
1394 ui.warn( |
1473 ui.warn( |
1395 _(b"warning: not updating public commit %s\n") |
1474 _(b"warning: not updating public commit %s\n") |
1412 |
1491 |
1413 newnode = new.commit() |
1492 newnode = new.commit() |
1414 |
1493 |
1415 mapping[old.node()] = [newnode] |
1494 mapping[old.node()] = [newnode] |
1416 |
1495 |
|
1496 if fold: |
|
1497 # Defer updating the (single) Diff until all nodes are |
|
1498 # collected. No tags were created, so none need to be |
|
1499 # removed. |
|
1500 newnodes.append(newnode) |
|
1501 continue |
|
1502 |
1417 _amend_diff_properties( |
1503 _amend_diff_properties( |
1418 unfi, drevid, [newnode], diffmap[old.node()] |
1504 unfi, drevid, [newnode], diffmap[old.node()] |
1419 ) |
1505 ) |
1420 # Remove local tags since it's no longer necessary |
1506 |
1421 tagname = b'D%d' % drevid |
1507 # Remove local tags since it's no longer necessary |
1422 if tagname in repo.tags(): |
1508 tagname = b'D%d' % drevid |
1423 tags.tag( |
1509 if tagname in repo.tags(): |
1424 repo, |
1510 tags.tag( |
1425 tagname, |
1511 repo, |
1426 nullid, |
1512 tagname, |
1427 message=None, |
1513 nullid, |
1428 user=None, |
1514 message=None, |
1429 date=None, |
1515 user=None, |
1430 local=True, |
1516 date=None, |
|
1517 local=True, |
|
1518 ) |
|
1519 elif fold: |
|
1520 # When folding multiple commits into one review with |
|
1521 # --fold, track even the commits that weren't amended, so |
|
1522 # that their association isn't lost if the properties are |
|
1523 # rewritten below. |
|
1524 newnodes.append(old.node()) |
|
1525 |
|
1526 # If the submitted commits are public, no amend takes place so |
|
1527 # there are no newnodes and therefore no diff update to do. |
|
1528 if fold and newnodes: |
|
1529 diff = diffmap[old.node()] |
|
1530 |
|
1531 # The diff object in diffmap doesn't have the local commits |
|
1532 # because that could be returned from differential.creatediff, |
|
1533 # not differential.querydiffs. So use the queried diff (if |
|
1534 # present), or force the amend (a new revision is being posted.) |
|
1535 if not olddiff or set(newnodes) != getlocalcommits(olddiff): |
|
1536 _debug(ui, b"updating local commit list for D%d\n" % drevid) |
|
1537 _amend_diff_properties(unfi, drevid, newnodes, diff) |
|
1538 else: |
|
1539 _debug( |
|
1540 ui, |
|
1541 b"local commit list for D%d is already up-to-date\n" |
|
1542 % drevid, |
1431 ) |
1543 ) |
|
1544 elif fold: |
|
1545 _debug(ui, b"no newnodes to update\n") |
|
1546 |
1432 scmutil.cleanupnodes(repo, mapping, b'phabsend', fixphase=True) |
1547 scmutil.cleanupnodes(repo, mapping, b'phabsend', fixphase=True) |
1433 if wnode in mapping: |
1548 if wnode in mapping: |
1434 unfi.setparents(mapping[wnode][0]) |
1549 unfi.setparents(mapping[wnode][0]) |
1435 |
1550 |
1436 |
1551 |